/*
 * ftop - show progress of open files and file systems
 * Copyright (C) 2009  Jason Todd <jtodd1@earthlink.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* ftop.c - main source file for ftop */

#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <inttypes.h>       /* uint32_t, etc. */
#include <unistd.h>         /* size_t, stat, read, close, getpid, tcgetattr, */
                            /* tcsetattr, isatty */
#include <string.h>         /* memset, memcpy, strcpy, strchr, strrchr, */
                            /* strncmp, strstr, memcmp */
#include <stdlib.h>         /* malloc, free, strtoul, strtoull */
#include <sys/types.h>      /* stat, open, getpid, getpwnam */
#include <sys/stat.h>       /* stat, open */
#include <fcntl.h>          /* open */
#include <stdarg.h>         /* va_start, va_end */
#include <errno.h>          /* errno */
#include <time.h>           /* nanosleep, time, localtime, strftime, */
                            /* gettimeofday */
#include <sys/ioctl.h>      /* ioctl */
#include <termios.h>        /* tcgetattr, tcsetattr, ioctl */
#include <pwd.h>            /* getpwnam */
#include <ctype.h>          /* isprint, isspace */
#include <sys/time.h>       /* gettimeofday */

#include "ftop.h"
#include "options.h"
#include "snapshot.h"
#include "ui.h"
#include "help.h"
#include "version.h"
#include "log.h"

static const char *fd_name_strs[] = {"in", "out", "err"};
static const char *opt_mode_disp[] = {"r-", "-w", "rw"};

static int parse_cmdline(ftop_data *p, int argc, char **argv);
static int add_closed_file(ftop_data *p, process_info *proc, file_info *file);
static int show_message(ftop_data *p);
static int time_diff(struct timeval *tv_then, struct timeval *tv_now);
static int setup_prompt(ftop_data *p, char *prompt, size_t max, int type,
                        parse_function f);
static int show_output(ftop_data *p, int part);
static int show_file(ftop_data *p, file_info *file, int more_procs,
                     int more_files, int part);
static int have_key_command(ftop_data *p);
static int compare_proc_activity(const void *va, const void *vb);

int main(int argc, char *argv[])
{
    int old_mode;
    ftop_data *p;
    snapshot *s;
    struct timespec tv;
    uint32_t total_delay;

    if ((p = malloc(sizeof(*p))) == NULL)
    {
        fprintf(stderr, "Error: could not allocate main data structure\n");
        return 0;
    }

    memset(p, 0, sizeof(*p));
    set_defaults(p);

    if (!parse_cmdline(p, argc, argv) != 0)
    {
        free(p);
        return 1;
    }

    if (!init_terminal(p))
    {
        free(p);
        return 1;
    }

    p->s_idx = 0;
    total_delay = 0;
    for (p->iter = 1;
         (p->opt_iterations == 0) || (p->iter <= p->opt_iterations); p->iter++)
    {
        s = &(p->s[p->s_idx]);
        s->log_flags = p->opt_log;
        get_snapshot(s);

        if (!update_fstype(p))
            break;

        if (!update_add(p))
            break;

        if (!show_output(p, 1))
            break;

        if (p->scrolled_rows > (p->output_rows - SCROLL_BASE))
        {
            p->scrolled_rows = p->output_rows - p->term_size.ws_row;
            if (!show_output(p, 1))
                break;
        }

        for (; total_delay < p->opt_delay_ms; total_delay += 10)
        {
            if (have_key_command(p))
                break;

            tv.tv_sec = 0;
            tv.tv_nsec = 10000000;
            nanosleep(&tv, NULL);
        }

        if (!show_output(p, 2))
            break;

        if (p->opt_quit)
            break;

        if (p->opt_switch_output)
        {
            p->opt_switch_output = 0;
            old_mode = p->mode;
            if (!restore_terminal(p))
                break;

            p->mode = 3 - old_mode;
            if (!init_terminal(p))
                break;
        }

        if (p->opt_refresh)
        {
            p->opt_refresh = 0;
            p->iter--;
        }
        else
        {
            total_delay = 0;
        }

        p->s_idx = 1 - p->s_idx;
    }

    p->opt_quit = 1;
    restore_terminal(p);
    return 0;
}

static int show_output(ftop_data *p, int part)
{
    int file_mode, opt_expand_atend, row3_col;
    size_t i, cols, proc_num, file_num, prev_idx, num_show_files;
    snapshot *s, *s_prev;
    process_info *proc, *prev_proc;
    file_info *file, *tmp_file;
    time_t t;
    char tmp_buf[80];
    char *next, *tmp_str;
    pid_t last_pid;

    num_show_files = 0;
    p_refresh(p);

    if (part == 1)
    {
        p_move(p, 0, 0);
        if (p->mode != MODE_FULL)
            printf("\n");

        p->output_rows = 0;
        p->do_printf = 1;
    }
    else
    {
        if (p->mode != MODE_LIMITED)
            return 1;

        p->shift_rows = p->cur_row;
        p->cur_row = 0;
        p->do_printf = 0;
        if ((p->prompt != NULL) || (p->error != NULL))
            printf("\n");
    }

    s = &(p->s[p->s_idx]);
    s_prev = &(p->s[1 - p->s_idx]);

    t = time(NULL);
    cols = strftime(tmp_buf, sizeof(tmp_buf), "%c", localtime(&t));
    if (cols == 0)
        cols = snprintf(tmp_buf, sizeof(tmp_buf), "%lus", t);

    if (p->opt_iterations != 0)
    {
        cols += snprintf(tmp_buf + cols, sizeof(tmp_buf) - cols,
                         ", iter %u/%u", p->iter, p->opt_iterations);
    }

    p_printf(p, "%s", tmp_buf);
    if ((p->mode == MODE_LIMITED) && (p->opt_log != 0) && (part == 1))
        fprintf(stderr, "%s\n", tmp_buf);

    cols = p->term_size.ws_col - 1 - cols - strlen(PACKAGE_STRING);
    p_repeat(p, cols, 3, ' ');
    p_printf(p, "%s", PACKAGE_STRING);
    p_eol(p, part);

    cols = snprintf(tmp_buf, sizeof(tmp_buf),
                    "Processes:  %u total, %u unreadable",
                    s->num_processes + s->num_unreadable_processes,
                    s->num_unreadable_processes);

    cols = p->term_size.ws_col - 1 - cols - strlen(HELP_OPT_STRING);
    p_printf(p, "%s", tmp_buf);
    if ((p->mode == MODE_LIMITED) && (p->opt_log != 0) && (part == 1))
        fprintf(stderr, "%s\n", tmp_buf);

    p_repeat(p, cols, 3, ' ');
    p_printf(p, "%s", HELP_OPT_PART1);
    p_attron(p, A_BOLD);
    p_printf(p, "%c", OPT_HELP_CHAR);
    p_attroff(p, A_BOLD);
    p_printf(p, "%s", HELP_OPT_PART2);
    p_attron(p, A_BOLD);
    p_printf(p, "%c", OPT_OPTIONS_CHAR);
    p_attroff(p, A_BOLD);
    p_printf(p, "%s", HELP_OPT_PART3);
    p_eol(p, part);

    snprintf(tmp_buf, sizeof(tmp_buf),
             "Open Files: %u regular, %u dir, %u chr, %u blk, %u pipe, %u sock, %u misc",
             s->num_reg, s->num_dir, s->num_chr, s->num_blk, s->num_pipe,
             s->num_sock, s->num_misc);

    p_printf(p, "%s", tmp_buf);
    p_eol(p, part);
    if ((p->error != NULL) && (time_diff(&(p->error_time), NULL) > 2500))
        p->error = NULL;

    if ((part == 1) && (p->mode == MODE_FULL))
        show_message(p);

    row3_col = p->cur_col;
    p_eol(p, part);

    if (p->opt_log != 0)
    {
        if ((p->mode == MODE_LIMITED) && (part == 1))
        {
            fprintf(stderr, "%s\nLog:\n", tmp_buf);
            if (s->log_used != 0)
            {
                fprintf(stderr, "%s\n", s->log);
                fflush(stderr);
            }
        }

        if (p->opt_view_log)
        {
            show_log(p);
            goto done;
        }
    }

    if (p->opt_help)
    {
        show_help(p, part);
        goto done;
    }
    else if (p->opt_options)
    {
        show_options(p, part);
        goto done;
    }
    else if (p->opt_version)
    {
        show_version(p, part);
        goto done;
    }

    cols = snprintf(tmp_buf, sizeof(tmp_buf),
                    "_  PID    #FD  USER      COMMAND");

    cols = p->term_size.ws_col - 1 - cols;
    p_attron(p, A_REVERSE);
    p_printf(p, "%s", tmp_buf);
    if (p->mode == MODE_FULL)
        p_repeat(p, cols, 0, ' ');

    p_attroff(p, A_REVERSE);
    p_eol(p, part);

    if (part != 1)
        goto skip;

    for (proc_num = 0; proc_num < s->num_processes; proc_num++)
    {
        proc = &(s->proc[proc_num]);
        prev_proc = NULL;
        for (prev_idx = 0; prev_idx < s_prev->num_processes; prev_idx++)
        {
            if (s_prev->proc[prev_idx].pid == proc->pid)
            {
                prev_proc = &(s_prev->proc[prev_idx]);
                proc->closed = prev_proc->closed;
                prev_proc->closed = NULL;   /* see garbage collection below */
                proc->num_closed = prev_proc->num_closed;
                proc->last_closed = prev_proc->last_closed;

                if (proc->closed != NULL)
                {
                    tmp_str = proc->closed[0].path;
                    if (((file_info *)tmp_str - proc->closed) != p->opt_closed)
                    {
                        free(proc->closed);
                        proc->closed = NULL;
                        proc->num_closed = 0;
                    }
                }

                for (file_num = 0; file_num < prev_proc->num_files;
                     file_num++)
                {
                    tmp_file = &(prev_proc->file[file_num]);
                    for (i = 0; i < proc->num_files; i++)
                    {
                        file = &(proc->file[i]);
                        if (file->fd == tmp_file->fd)
                        {
                            file->prev = tmp_file;
                            if (strcmp(file->path, tmp_file->path) != 0)
                            {
                                file->prev_path_changed = 1;
                                add_closed_file(p, proc, tmp_file);
                            }
                            else
                            {
                                calc_rate(file, &(s_prev->tv), &(s->tv));
                            }

                            break;
                        }
                    }

                    if ((proc->num_files != 0) && (i == proc->num_files))
                        add_closed_file(p, proc, tmp_file);
                }

                break;
            }
        }

        if (p->opt_procs_pid != NULL)
        {
            /* having a PID buffer means we also have a keyword buffer */

            for (i = 0; p->opt_procs_pid[i] != 0; i++)
            {
                if (proc->pid == p->opt_procs_pid[i])
                    break;
            }

            if (p->opt_procs_pid[i] == 0)
            {
                next = p->opt_procs_keyword;
                while (*next != '\0')
                {
                    if (strstr(proc->cmdline, next) != NULL)
                        break;

                    next += (strlen(next) + 1);
                }

                if (*next == '\0')
                    continue;
            }
        }

        if (p->opt_uid_specified && (proc->uid != p->opt_uid))
            continue;

        if (proc->closed != NULL)
            proc->show = 1;

        proc->last_file = proc->num_files - 1;
        for (file_num = 0; file_num < proc->num_files; file_num++)
        {
            file = &(proc->file[file_num]);
            if (p->opt_file != NULL)
            {
                next = p->opt_file;
                while (*next != '\0')
                {
                    if (strstr(file->path, next) != NULL)
                        break;

                    next += (strlen(next) + 1);
                }

                if (*next == '\0')
                    continue;
            }

            file_mode = file->fdinfo_flags & O_ACCMODE;
            if ((file_mode != p->opt_mode[0]) &&
                (file_mode != p->opt_mode[1]) &&
                (file_mode != p->opt_mode[2]))
            {
                continue;
            }

            file_mode = file->st.st_mode;
            if ((S_ISREG(file_mode) && (strchr(p->opt_type, 'f') == NULL)) ||
                (S_ISDIR(file_mode) && (strchr(p->opt_type, 'd') == NULL)) ||
                (S_ISCHR(file_mode) && (strchr(p->opt_type, 'c') == NULL)) ||
                (S_ISBLK(file_mode) && (strchr(p->opt_type, 'b') == NULL)) ||
                (S_ISFIFO(file_mode) && (strchr(p->opt_type, 'p') == NULL)) ||
                (S_ISSOCK(file_mode) && (strchr(p->opt_type, 's') == NULL)) ||
                ((file->flags & FLAG_MISC) &&
                 (strchr(p->opt_type, 'x') == NULL)))
            {
                continue;
            }

            file->flags |= FLAG_SHOW;
            proc->show = 1;
            num_show_files++;

            if ((((file->fdinfo_flags & O_ACCMODE) == O_WRONLY) ||
                 ((file->fdinfo_flags & O_ACCMODE) == O_RDWR)) &&
                (p->opt_size_val != NULL))
            {
                /* having a size buffer means we also have a keyword buffer */

                next = p->opt_size_keyword;
                for (i = 0; p->opt_size_val[i] != 0; i++)
                {
                    if (strstr(file->path, next) != NULL)
                    {
                        file->st.st_size = p->opt_size_val[i];
                        break;
                    }

                    next += (strlen(next) + 1);
                }
            }

            if (!(p->opt_unexp_files))
                proc->last_file = file_num;
        }
    }

    for (prev_idx = 0; prev_idx < s_prev->num_processes; prev_idx++)
    {
        proc = &(s_prev->proc[prev_idx]);
        if (proc->closed != NULL)
        {
            free(proc->closed);                 /* garbage collection */
            proc->closed = NULL;
        }
    }

    qsort(s->proc, s->num_processes, sizeof(*(s->proc)),
          compare_proc_activity);

skip:
    if (p->opt_add_info != NULL)
    {
        if (!(p->opt_add_only))
            p_printf(p, "-- ");

        p_attron(p, A_BOLD);
        p_printf(p, "%siles & file systems (press %c to modify)",
                 (p->opt_add_only ? "F" : "Additional f"), OPT_ADD_CHAR);

        p_attroff(p, A_BOLD);
        p_eol(p, part);
        opt_expand_atend = p->opt_expand_atend;
        p->opt_expand_atend = 1;
        for (i = 0; p->opt_add_info[i].path != NULL; i++)
        {
            show_file(p, &(p->opt_add_info[i]),
                      (((num_show_files != 0) || p->opt_unexp_procs) &&
                       !(p->opt_add_only)),
                      (p->opt_add_info[i + 1].path != NULL), part);
        }

        p->opt_expand_atend = opt_expand_atend;
        if (p->opt_add_only)
            goto done;
    }

    if (s->num_processes == 0)
        goto done;

    last_pid = s->proc[s->num_processes - 1].pid;
    if (!(p->opt_unexp_procs))
    {
        for (proc_num = 0; proc_num < s->num_processes; proc_num++)
        {
            if (s->proc[proc_num].show)
                last_pid = s->proc[proc_num].pid;
        }
    }

    for (proc_num = 0; proc_num < s->num_processes; proc_num++)
    {
        proc = &(s->proc[proc_num]);
        if (!(proc->show) && !(p->opt_unexp_procs))
            continue;

        p_printf(p, "%c- ", ((proc->show || p->opt_unexp_files) ? '-' : '+'));
        p_attron(p, A_BOLD);
        p_printf(p, "%-5u  %-3u  ", proc->pid, proc->num_files);
        if ((tmp_str = lookup_user(p, proc->uid)) != NULL)
            p_printf(p, "%-8s", tmp_str);
        else
            p_printf(p, "%-8u", proc->uid);

        p_printf(p, "  %s", proc->cmdline);
        p_attroff(p, A_BOLD);
        p_eol(p, part);

        for (file_num = 0; file_num < proc->num_files; file_num++)
        {
            file = &(proc->file[file_num]);
            if (!(file->flags & FLAG_SHOW) && !(p->opt_unexp_files))
                continue;

            show_file(p, file, (proc->pid != last_pid),
                      ((file_num != proc->last_file) ||
                       (proc->num_closed != 0)), part);
        }

        if ((p->opt_closed == 0) || (proc->closed == NULL))
            continue;

        for (i = 0; i < proc->num_closed; i++)
        {
            file = &(proc->closed[(proc->last_closed + i) % p->opt_closed]);
            show_file(p, file, (proc->pid != last_pid),
                      (i != (proc->num_closed - 1)), part);
        }
    }

done:
    p_endoutput(p);
    if (p->mode == MODE_FULL)
        p_move(p, 3, row3_col);

    p->do_printf = 1;
    if ((part == 1) && (p->mode != MODE_FULL))
        show_message(p);

    p_refresh(p);
    if (num_show_files != 0)
    {
        p->had_match = 1;
        p->no_match_iters = 0;
    }
    else if (p->opt_max_no_match != 0)
    {
        p->no_match_iters++;
        if (p->had_match && (p->no_match_iters >= p->opt_max_no_match))
            p->opt_quit = 1;
    }

    return 1;
}

static int add_closed_file(ftop_data *p, process_info *proc, file_info *file)
{
    size_t i, bytes;
    file_info *closed;
    char *path_buf;

    if (p->opt_closed == 0)
        return 1;

    if (proc->closed == NULL)
    {
        bytes = p->opt_closed * (sizeof(*proc->closed) + MAX_CLOSED_PATH);
        proc->closed = malloc(bytes);
        if (proc->closed == NULL)
            return 0;

        proc->num_closed = 0;
        proc->last_closed = p->opt_closed;
        for (i = 0; i < p->opt_closed; i++)
        {
            proc->closed[i].path = (char *)(proc->closed + p->opt_closed) +
                                   (i * MAX_CLOSED_PATH);
        }
    }

    if (proc->last_closed == 0)
        proc->last_closed = p->opt_closed;

    closed = &(proc->closed[--(proc->last_closed)]);
    path_buf = closed->path;
    memcpy(closed, file, sizeof(*file));
    closed->path = path_buf;

    bytes = strlen(file->path);
    if (bytes >= MAX_CLOSED_PATH)
        bytes = MAX_CLOSED_PATH - 1;

    strncpy(closed->path, file->path, bytes);
    closed->path[bytes] = '\0';

    closed->flags |= FLAG_CLOSED;

    if (proc->num_closed < p->opt_closed)
        proc->num_closed++;

    return 1;
}

int calc_rate(file_info *file, struct timeval *tv_then,
              struct timeval *tv_now)
{
    int ms;
    double avg_rate;
    off_t cur_rate;

    file->prev_pos = file->prev->pos;
    if (file->pos > file->prev_pos)
    {
        cur_rate = file->pos - file->prev_pos;
        ms = time_diff(tv_then, tv_now);
        if ((ms < 1000) && (ms != 0))
            cur_rate *= (1000 / ms);
        else if (ms > 1000)
            cur_rate /= ((ms * 1000) / 1000000);

        if (file->prev->avg_rate == 0)
            file->avg_rate = cur_rate;

        gettimeofday(&(file->last_activity), NULL);
    }
    else
    {
        cur_rate = 0;
    }

    avg_rate = (0.7 * file->prev->avg_rate) + (0.3 * cur_rate);
    file->avg_rate = avg_rate;
    return 1;
}

int screen_title(ftop_data *p, char *title, char return_key)
{
    size_t cols;
    char tmp_buf[80];

    p_attron(p, A_REVERSE);
    cols = snprintf(tmp_buf, sizeof(tmp_buf), "%s (press ", title);
    p_printf(p, tmp_buf);
    p_attron(p, A_BOLD);
    p_printf(p, "%c", return_key);
    p_attroff(p, A_BOLD);
    cols += snprintf(tmp_buf, sizeof(tmp_buf), " to return)");
    p_printf(p, tmp_buf);
    cols = p->term_size.ws_col - cols;
    if (p->mode == MODE_FULL)
        p_repeat(p, cols, 0, ' ');

    p_attroff(p, A_REVERSE);
    p_eol(p, 1);

    return 1;
}

static int show_message(ftop_data *p)
{
    if (p->error != NULL)
    {
        p_attron(p, A_BOLD);
        p_printf(p, p->error, p->input_buf);
        p_attroff(p, A_BOLD);
    }
    else if (p->prompt != NULL)
    {
        p_printf(p, "%s%s", p->prompt, p->input_buf);
    }
    else
    {
        return 0;
    }

    return 1;
}

static int time_diff(struct timeval *tv_then, struct timeval *tv_now)
{
    struct timeval tv_result;

    if (tv_now == NULL)
        gettimeofday(&tv_result, NULL);
    else
        memcpy(&tv_result, tv_now, sizeof(tv_result));

    if (tv_result.tv_usec < tv_then->tv_usec)
    {
        tv_result.tv_sec--;
        tv_result.tv_sec -= tv_then->tv_sec;
        tv_result.tv_usec = (tv_result.tv_usec - tv_then->tv_usec) + 1000000;
    }
    else
    {
        tv_result.tv_sec -= tv_then->tv_sec;
        tv_result.tv_usec -= tv_then->tv_usec;
    }

    return ((tv_result.tv_sec * 1000) + (tv_result.tv_usec / 1000));
}

static int show_file(ftop_data *p, file_info *file, int more_procs,
              int more_files, int part)
{
    size_t i, j, bytes, bar_total;
    char more_procs_ch, more_files_ch, expanded_ch;
    char tmp_buf[80], rate_buf[80];
    char *tmp_str;
    snapshot *s, *s_prev;
    file_info *tmp_file;
    off_t bar_used, rem_seconds;

    s = &(p->s[p->s_idx]);
    s_prev = &(p->s[1 - p->s_idx]);

    more_procs_ch = (more_procs ? '|' : ' ');
    more_files_ch = (more_files ? '|' : ' ');
    if ((file->flags & FLAG_SHOW) && !(file->flags & FLAG_CLOSED) &&
        (S_ISREG(file->st.st_mode) || (file->flags & FLAG_FS)) &&
        (p->opt_expand_atend || (file->pos < file->st.st_size)) &&
        (p->opt_expand_atbegin || (file->pos > 0)))
    {
        expanded_ch = '-';
    }
    else
    {
        expanded_ch = '+';
    }

    if ((p->opt_add_info == NULL) || !(p->opt_add_only))
        p_printf(p, "%c  ", more_procs_ch);

    p_printf(p, "%c- ", expanded_ch);
    if ((p->opt_fd_numbers) || (file->fd > 2) || (file->flags & FLAG_ADD))
        p_printf(p, "%-3u  ", file->fd);
    else
        p_printf(p, "%-3s  ", fd_name_strs[file->fd]);

    if (file->flags & FLAG_FS)
    {
        snprintf(tmp_buf, sizeof(tmp_buf), "FS-");
    }
    else
    {
        if (S_ISREG(file->st.st_mode))
            p_printf(p, "-");
        else if (S_ISDIR(file->st.st_mode))
            p_printf(p, "d");
        else if (S_ISCHR(file->st.st_mode))
            p_printf(p, "c");
        else if (S_ISBLK(file->st.st_mode))
            p_printf(p, "b");
        else if (S_ISFIFO(file->st.st_mode))
            p_printf(p, "p");
        else if (S_ISSOCK(file->st.st_mode))
            p_printf(p, "s");
        else
            p_printf(p, "?");

        snprintf(tmp_buf, sizeof(tmp_buf), "%s",
                 opt_mode_disp[file->fdinfo_flags & O_ACCMODE]);

        if (file->fdinfo_flags & O_APPEND)
            tmp_buf[1] = 'W';
    }

    p_printf(p, "%s  ", tmp_buf);
    snprintf(tmp_buf, sizeof(tmp_buf), "--");
    if (file->flags & FLAG_CLOSED)
    {
        p_printf(p, "XX");
    }
    else if (file->prev_path_changed)
    {
        p_printf(p, "**");
    }
    else if (file->prev != NULL)
    {
        if (((file->pos > file->prev->pos) &&
             (file->prev->pos < file->prev->prev_pos)) ||
            ((file->pos < file->prev->pos) &&
             (file->prev->pos > file->prev->prev_pos)))
        {
            tmp_buf[0] = '<';
            tmp_buf[1] = '>';
        }
        else if (file->pos > file->prev->pos)
        {
            tmp_buf[0] = '>';
            if (file->prev->pos > file->prev->prev_pos)
                tmp_buf[1] = '>';
        }
        else if (file->pos < file->prev_pos)
        {
            tmp_buf[1] = '<';
            if (file->prev->pos < file->prev->prev_pos)
                tmp_buf[0] = '<';
        }
        else if (file->prev->prev != NULL)
        {
            if (file->prev->pos > file->prev->prev_pos)
                tmp_buf[1] = '>';
            else if (file->prev->pos < file->prev->prev_pos)
                tmp_buf[0] = '<';
        }

        p_printf(p, tmp_buf);
    }
    else
    {
        p_printf(p, tmp_buf);
    }

    label_value(p, tmp_buf, sizeof(tmp_buf), file->pos);
    p_printf(p, "  %7s", tmp_buf);

    label_value(p, tmp_buf, sizeof(tmp_buf), file->st.st_size);
    if (p->opt_hide_dir && !(file->flags & FLAG_MISC) &&
        ((tmp_str = strrchr(file->path, '/')) != NULL))
    {
        tmp_str++;
    }
    else
    {
        tmp_str = file->path;
    }

    p_printf(p, "/%-7s  %s", tmp_buf, tmp_str);

    if (S_ISREG(file->st.st_mode) || S_ISFIFO(file->st.st_mode) ||
        S_ISSOCK(file->st.st_mode))
    {
        for (i = 0; i < s->num_processes; i++)
        {
            for (j = 0; j < s->proc[i].num_files; j++)
            {
                tmp_file = &(s->proc[i].file[j]);
                if ((strcmp(tmp_file->path, file->path) == 0) &&
                    (tmp_file != file))
                {
                    if ((p->opt_fd_numbers) || (tmp_file->fd > 2))
                    {
                        snprintf(tmp_buf, sizeof(tmp_buf), "fd %u for",
                                 tmp_file->fd);
                    }
                    else
                    {
                        snprintf(tmp_buf, sizeof(tmp_buf), "std%s %s",
                                 fd_name_strs[tmp_file->fd],
                                 ((tmp_file->fd == 0) ? "to" : "from"));
                    }

                    p_printf(p, " (%s PID %u)", tmp_buf,
                             s->proc[i].pid);

                    break;
                }
            }

            if (j < s->proc[i].num_files)
                break;
        }
    }

    p_eol(p, part);
    if (expanded_ch != '-')
        return 1;

    if ((p->opt_add_info == NULL) || !(p->opt_add_only))
        p_printf(p, "%c  ", more_procs_ch);

    p_printf(p, "%c  [", more_files_ch);
    bar_total = p->term_size.ws_col - 1 - p->cur_col - 1;

    if (file->pos > file->st.st_size)
        file->st.st_size = file->pos;

    if (file->st.st_size != 0)
        bar_used = (file->pos * bar_total) / file->st.st_size;
    else
        bar_used = 0;

    label_value(p, rate_buf, sizeof(rate_buf), file->avg_rate);
    if (p->opt_quiet)
    {
        tmp_buf[0] = '\0';
        bytes = 0;
    }
    else if ((file->avg_rate != 0) &&
             (time_diff(&(file->last_activity), NULL) < ACTIVITY_TIMEOUT))
    {
        rem_seconds = (file->st.st_size - file->pos) / file->avg_rate;
        tmp_str = " %s/s, %" FMT_OFF_T ":%02" FMT_OFF_T " remain";
        bytes = snprintf(tmp_buf, sizeof(tmp_buf), tmp_str, rate_buf,
                         rem_seconds / 60, rem_seconds % 60);
    }
    else
    {
        bytes = snprintf(tmp_buf, sizeof(tmp_buf), " (stalled)");
    }

    if (p->mode == MODE_FULL)
    {
        if (bar_used > 0)
        {
            snprintf(rate_buf, bar_used + 1, "%s", tmp_buf);
            p_attron(p, A_REVERSE);
            p_printf(p, "%s", rate_buf);
            if (bar_used > bytes)
                p_repeat(p, bar_used - bytes, 0, ' ');

            p_attroff(p, A_REVERSE);
        }

        if (bar_used < bytes)
        {
            p_printf(p, "%s", tmp_buf + bar_used);
            p_repeat(p, bar_total - bytes, 0, ' ');
        }
        else
        {
            p_repeat(p, bar_total - bar_used, 0, ' ');
        }
    }
    else
    {
        if (bar_used < (bar_total / 2))
        {
            p_repeat(p, bar_used, 0, '#');
            p_printf(p, "%s", tmp_buf);
        }
        else
        {
            p_printf(p, "%s ", tmp_buf);
            bytes++;
            bar_used -= bytes;
            p_repeat(p, bar_used, 0, '#');
        }

        p_repeat(p, bar_total - bar_used - bytes, 0, ' ');
    }

    p_printf(p, "]");
    p_eol(p, part);
    return 1;
}

int label_value(ftop_data *p, char *buf, size_t max, off_t value)
{
    off_t mb, gb;
    char *fmt, *mb_fmt, *gb_fmt;

    fmt = "%" FMT_OFF_T;
    if (p->opt_units_base10)
    {
        mb = 1000000;
        gb = 1000 * mb;
        mb_fmt = "%" FMT_OFF_T ".%" FMT_OFF_T "e6";
        gb_fmt = "%" FMT_OFF_T ".%" FMT_OFF_T "e9";
    }
    else
    {
        mb = 1048576;
        gb = 1024 * mb;
        mb_fmt = "%" FMT_OFF_T ".%" FMT_OFF_T "M";
        gb_fmt = "%" FMT_OFF_T ".%" FMT_OFF_T "G";
    }

    if (value < mb)
        snprintf(buf, max, fmt, value);
    else if (value < gb)
        snprintf(buf, max, mb_fmt, value / mb, ((value * 10) / mb) % 10);
    else
        snprintf(buf, max, gb_fmt, value / gb, ((value * 10) / gb) % 10);

    return 1;
}

static int setup_prompt(ftop_data *p, char *prompt, size_t max, int type,
                 parse_function f)
{
    size_t idx;

    p->prompt = prompt;
    p->input_max = max;
    p->input_type = type;
    p->parse_input = f;
    p->input_dots = 0;
    p->input_need_eq = 1;

    for (idx = 0; p->input_buf[idx] != '\0'; idx++)
    {
        if (p->input_buf[idx] == '.')
            p->input_dots++;
        else if (p->input_buf[idx] == '=')
            p->input_need_eq = 0;
        else if (p->input_buf[idx] == ',')
            p->input_need_eq = 1;
    }

    show_message(p);
    return 1;
}

static int have_key_command(ftop_data *p)
{
    int c, ret;
    char *prev_error;

    if (!p_getkey(p, &c))
        return 0;

    ret = 1;
    if (p->prompt != NULL)
    {
        prev_error = p->error;
        p->error = NULL;
        if (p_input_key(p, c) && (prev_error != NULL))
        {
            if (c == '\n')
            {
                p->error = NULL;
                if (p->mode == MODE_FULL)
                    p_move(p, 3, 0);
            }

            p_eol(p, 1);
            if (p->mode == MODE_FULL)
                p_move(p, 3, 0);

            show_message(p);
            p_refresh(p);
        }

        if ((p->prompt == NULL) || (p->error != NULL))
            return ret;

        return 0;
    }

    switch (c)
    {
        case KEY_DOWN:
            if (p->scrolled_rows < (p->output_rows - p->term_size.ws_row))
                p->scrolled_rows++;

            break;
        case KEY_UP:
            if (p->scrolled_rows > 0)
                p->scrolled_rows--;

            break;
        case ' ':
            if (p->mode != MODE_FULL)
                break;
        case KEY_NPAGE:
            if (p->scrolled_rows < (p->output_rows - p->term_size.ws_row))
                p->scrolled_rows += (p->term_size.ws_row - SCROLL_BASE);

            break;
        case '-':
            if (p->mode != MODE_FULL)
                break;
        case KEY_PPAGE:
            if (p->scrolled_rows > (p->term_size.ws_row - SCROLL_BASE))
                p->scrolled_rows -= (p->term_size.ws_row - SCROLL_BASE);
            else
                p->scrolled_rows = 0;

            break;
        case KEY_HOME:
            p->scrolled_rows = 0;
            break;
        case KEY_END:
            p->scrolled_rows = p->output_rows - p->term_size.ws_row;
            break;

        OPTION_CASES

        default:
            ret = 0;
    }

    return ret;
}

static int compare_proc_activity(const void *va, const void *vb)
{
    size_t i, a_change, b_change;
    process_info *a, *b;
    off_t pct, a_max_pct, b_max_pct, a_max_rate, b_max_rate;

    a = (process_info *)va;
    b = (process_info *)vb;
    a_max_pct = 0;
    b_max_pct = 0;
    a_max_rate = 0;
    b_max_rate = 0;
    a_change = 0;
    b_change = 0;

    for (i = 0; i < a->num_files; i++)
    {
        if ((a->file[i].avg_rate > a_max_rate) &&
            (time_diff(&(a->file[i].last_activity), NULL) < ACTIVITY_TIMEOUT))
        {
            a_max_rate = a->file[i].avg_rate;
        }

        if (a->file[i].st.st_size != 0)
        {
            pct = (a->file[i].pos * 100) / a->file[i].st.st_size;
            if (pct > a_max_pct)
                a_max_pct = pct;
        }

        if (a->file[i].prev_path_changed)
            a_change++;
    }

    for (i = 0; i < b->num_files; i++)
    {
        if ((b->file[i].avg_rate > b_max_rate) &&
            (time_diff(&(b->file[i].last_activity), NULL) < ACTIVITY_TIMEOUT))
        {
            b_max_rate = b->file[i].avg_rate;
        }

        if (b->file[i].st.st_size != 0)
        {
            pct = (b->file[i].pos * 100) / b->file[i].st.st_size;
            if (pct > b_max_pct)
                b_max_pct = pct;
        }

        if (b->file[i].prev_path_changed)
            b_change++;
    }

    if (a_max_rate != b_max_rate)
        return ((int)b_max_rate - (int)a_max_rate);

    if (a_change != b_change)
        return ((int)b_change-(int)a_change);

    return ((int)b_max_pct - (int)a_max_pct);
}

int compare_file_activity(const void *va, const void *vb)
{
    file_info *a, *b;
    off_t a_rate, b_rate, a_pct, b_pct;

    a = (file_info *)va;
    b = (file_info *)vb;
    a_rate = 0;
    b_rate = 0;

    if ((a->avg_rate != 0) &&
        (time_diff(&(a->last_activity), NULL) < ACTIVITY_TIMEOUT))
    {
        a_rate = a->avg_rate;
    }

    if ((b->avg_rate != 0) &&
        (time_diff(&(b->last_activity), NULL) < ACTIVITY_TIMEOUT))
    {
        b_rate = b->avg_rate;
    }

    if (a_rate == b_rate)
    {
        if (a->st.st_size != 0)
            a_pct = (a->pos * 100) / a->st.st_size;
        else
            a_pct = 0;

        if (b->st.st_size != 0)
            b_pct = (b->pos * 100) / b->st.st_size;
        else
            b_pct = 0;

        return ((int)(b_pct) - (int)(a_pct));
    }

    return ((int)(b_rate) - (int)(a_rate));
}

static int parse_cmdline(ftop_data *p, int argc, char **argv)
{
    int i, ret;
    size_t j, len, screens;
    char c;
    char *param;

    for (i = 1; i < argc; i++)
    {
        if (argv[i][0] != '-')
        {
            fprintf(stderr, "Error: invalid command line argument '%s'\n",
                    argv[i]);

            return 0;
        }

        len = strlen(argv[i]);
        for (j = 1; j < len; j++)
        {
            c = argv[i][j];
            if (argv[i][j + 1] == '\0')
                param = argv[i + 1];
            else
                param = &(argv[i][j + 1]);

            ret = check_opt_char(p, c, param);
            if (ret == -1)
            {
                fprintf(stderr, "Error: invalid command line option -%c\n", c);
                return 0;
            }
            else if (ret == 0)
            {
                if (param == NULL)
                {
                    fprintf(stderr, "Error: missing value for option -%c\n",
                            c);
                }
                else
                {
                    fprintf(stderr,
                            "Error: invalid value '%s' for option -%c\n",
                            param, c);
                }

                return 0;
            }
            else if (ret == 1)
            {
                if (param == argv[i + 1])
                    i++;

                break;
            }
        }
    }

    screens = 0;
    if (p->opt_help)
        screens++;

    if (p->opt_options)
        screens++;

    if (p->opt_view_log)
        screens++;

    if (p->opt_version)
        screens++;

    if (screens > 1)
    {
        fprintf(stderr, "Error: multiple initial screens were specified\n");
        return 0;
    }

    return 1;
}

int add_lookup_user(ftop_data *p, struct passwd *pw)
{
    if (p->num_lookup_users < MAX_LOOKUP_USERS)
    {
        p->lookup_user_uid[p->num_lookup_users] = pw->pw_uid;
        snprintf(p->lookup_user_name[p->num_lookup_users++], 16, "%s",
                 pw->pw_name);

        return 1;
    }

    return 0;
}

char *lookup_user(ftop_data *p, uid_t uid)
{
    size_t i;
    struct passwd *pw;

    for (i = 0; i < p->num_lookup_users; i++)
    {
        if (p->lookup_user_uid[i] == uid)
            return p->lookup_user_name[i];
    }

    if ((pw = getpwuid(uid)) == NULL)
        return NULL;

    if (!add_lookup_user(p, pw))
        return NULL;

    return p->lookup_user_name[p->num_lookup_users - 1];
}
