/*
 * 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/>.
 */

/* options.c - ftop option handling and options screen */

#define _FILE_OFFSET_BITS 64
#include <string.h>         /* strlen, strcmp, memset, memcpy, strrchr, */
                            /* strstr, memcmp, strchr */
#include <stdlib.h>         /* malloc, free, strtoul, strtoull, qsort */
#include <fcntl.h>          /* O_WRONLY, open, O_RDONLY */
#include <sys/types.h>      /* getpwnam, stat, open, fstat */
#include <pwd.h>            /* getpwnam */
#include <stdio.h>          /* snprintf */
#include <sys/stat.h>       /* stat, S_ISDIR, open, fstat, read */
#include <unistd.h>         /* stat, fstat, close */
#include <sys/statvfs.h>    /* statvfs */

#include "ftop.h"
#include "ui.h"
#include "options.h"

/* note, special keyword "all" */
static const char *opt_log_strs[] =
    {"none", "err", "warn", "info", "debug", "all", NULL};

static const int opt_log_vals[] =
    {LOG_NONE, LOG_ERR, LOG_WARN, LOG_INFO, LOG_DEBUG};

/* note, special keyword "all" */
static const char *opt_mode_strs[] = {"r", "w", "rw", "all", NULL};
static const int opt_mode_vals[] = {O_RDONLY, O_WRONLY, O_RDWR};

/* note, special keyword "all" */
/* 'r' (regular) is a synonym for 'f' (file) */
static const char *opt_type_strs[] =
    {"f", "d", "c", "b", "p", "s", "x", "r", "all", NULL};

static int parse_comma_list(char *in_str, char **out_buf, size_t *num_items,
                            const char **valid_words);

int set_defaults(ftop_data *p)
{
    p->opt_delay_ms = 1000;
    p->opt_type[0] = 'f';
    p->opt_type[1] = 'd';
    p->opt_mode[0] = opt_mode_vals[0];
    p->opt_mode[1] = opt_mode_vals[1];
    p->opt_mode[2] = opt_mode_vals[2];

    return 1;
}

int check_opt_char(ftop_data *p, char c, char *param)
{
    int ret;

    ret = 2;                    /* 2: a boolean option was processed */

    if (c == OPT_ADD_CHAR)
        ret = parse_add(p, param);
    else if (c == OPT_ADD_ONLY_CHAR)
        p->opt_add_only = 1;
    else if (c == OPT_EXPAND_ATBEGIN_CHAR)
        p->opt_expand_atbegin = 1;
    else if (c == OPT_CLOSED_CHAR)
        ret = parse_closed(p, param);
    else if (c == OPT_DELAY_CHAR)
        ret = parse_delay(p, param);
    else if (c == OPT_HIDE_DIR_CHAR)
        p->opt_hide_dir = 1;
    else if (c == OPT_EXPAND_ATEND_CHAR)
        p->opt_expand_atend = 1;
    else if (c == OPT_FILE_CHAR)
        ret = parse_file(p, param);
    else if (c == OPT_UNEXP_FILES_CHAR)
        p->opt_unexp_files = 1;
    else if (c == OPT_HELP_CHAR)
        p->opt_help = 1;
    else if (c == OPT_LOG_CHAR)
        ret = parse_log(p, param);
    else if (c == OPT_VIEW_LOG_CHAR)
        p->opt_view_log = 1;
    else if (c == OPT_MODE_CHAR)
        ret = parse_mode(p, param);
    else if (c == OPT_ITERATIONS_CHAR)
        ret = parse_iterations(p, param);
    else if (c == OPT_FD_NUMBERS_CHAR)
        p->opt_fd_numbers = 1;
    else if (c == OPT_OPTIONS_CHAR)
        p->opt_options = 1;
    else if (c == OPT_SWITCH_OUTPUT_CHAR)
        p->mode = MODE_LIMITED;
    else if (c == OPT_PROCS_CHAR)
        ret = parse_procs(p, param);
    else if (c == OPT_UNEXP_PROCS_CHAR)
        p->opt_unexp_procs = 1;
    else if (c == OPT_QUIT_CHAR)
        p->opt_quit = 1;
    else if (c == OPT_QUIET_CHAR)
        p->opt_quiet = 1;
    else if (c == OPT_REFRESH_CHAR)
        p->opt_refresh = 1;
    else if (c == OPT_TYPE_CHAR)
        ret = parse_type(p, param);
    else if (c == OPT_UID_CHAR)
        ret = parse_uid(p, param);
    else if (c == OPT_UNITS_BASE10_CHAR)
        p->opt_units_base10 = 1;
    else if (c == OPT_VERSION_CHAR)
        p->opt_version = 1;
    else if (c == OPT_MAX_NO_MATCH_CHAR)
        ret = parse_max_no_match(p, param);
    else if (c == OPT_FSTYPE_CHAR)
        ret = parse_fstype(p, param);
    else if (c == OPT_SIZE_CHAR)
        ret = parse_size(p, param);
    else
        ret = -1;

    return ret;
}

static int parse_comma_list(char *in_str, char **out_buf, size_t *num_items,
                            const char **valid_words)
{
    size_t i, j, len, num;
    char *buf, *next;

    if (in_str == NULL)
        return 0;

    len = strlen(in_str);
    if ((buf = malloc(len + 2)) == NULL)    /* room for 2 NULS at end */
        return 0;

    *out_buf = buf;
    num = 0;
    for (i = 0; i <= len; i++)
    {
        if ((in_str[i] == ',') || (in_str[i] == '\0'))
        {
            if ((i == 0) || (in_str[i - 1] == ',') || (i == (len - 1)))
            {
                free(buf);
                return 0;
            }

            buf[i] = '\0';
            num++;
        }
        else
        {
            buf[i] = in_str[i];
        }
    }

    buf[++len] = '\0';
    if (num_items != NULL)
        *num_items = num;

    if (valid_words == NULL)
        return 1;

    next = buf;
    for (i = 0; i < num; i++)
    {
        for (j = 0; valid_words[j] != NULL; j++)
        {
            if (strcmp(next, valid_words[j]) == 0)
                break;
        }

        if (valid_words[j] == NULL)
        {
            free(buf);
            return 0;
        }

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

    return 1;
}

int parse_add(ftop_data *p, char *str)
{
    size_t num;
    char *add_buf, *next;
    file_info *file, *prev_file;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_ADD;

    if (str[0] == '\0')
    {
        free(p->opt_add_prev);
        free(p->opt_add_info);
        free(p->opt_add_path);
        p->opt_add_prev = NULL;
        p->opt_add_info = NULL;
        p->opt_add_path = NULL;
        if (p->mtab_size != 0)
        {
            free(p->mtab_buf);
            p->mtab_buf = NULL;
            p->mtab_size = 0;
            update_fstype(p);
        }

        return 1;
    }

    if (!parse_comma_list(str, &add_buf, &num, NULL))
        return 0;

    p->opt_add_path = add_buf;
    p->opt_add_info = malloc((num + 1) * sizeof(*(p->opt_add_info)));
    p->opt_add_prev = malloc((num + 1) * sizeof(*(p->opt_add_prev)));
    if ((num == 0) || (p->opt_add_info == NULL) || (p->opt_add_prev == NULL))
    {
        if (p->opt_add_prev != NULL)
            free(p->opt_add_prev);

        if (p->opt_add_info != NULL)
            free(p->opt_add_info);

        free(p->opt_add_path);
        p->opt_add_prev = NULL;
        p->opt_add_info = NULL;
        p->opt_add_path = NULL;
        return 0;
    }

    memset(p->opt_add_info, 0, (num + 1) * sizeof(*(p->opt_add_info)));
    memset(p->opt_add_prev, 0, (num + 1) * sizeof(*(p->opt_add_prev)));

    num = 0;
    for (next = p->opt_add_path; *next != '\0';
         next += (strlen(next) + 1))
    {
        file = &(p->opt_add_info[num]);
        prev_file = &(p->opt_add_prev[num]);

        file->path = next;
        file->fd = ++num;
        file->fdinfo_flags = O_WRONLY;
        file->flags = FLAG_ADD | FLAG_SHOW;
        if ((file->path[0] == '/') && (file->path[1] == '/'))
        {
            file->flags |= FLAG_FSTYPE;
            file->path++;
        }

        memcpy(prev_file, file, sizeof(*file));
        file->prev = prev_file;
    }

    if (p->mtab_size != 0)
    {
        free(p->mtab_buf);
        p->mtab_buf = NULL;
        p->mtab_size = 0;
    }

    update_add(p);
    for (num = 0; p->opt_add_info[num].path != NULL; num++)
        p->opt_add_info[num].avg_rate = 0;

    return 1;
}

int parse_closed(ftop_data *p, char *str)
{
    char *num_end;
    uint32_t opt_closed;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_CLOSED;

    opt_closed = strtoul(str, &num_end, 10);
    if ((num_end != str) && (*num_end == '\0'))
    {
        p->opt_closed = opt_closed;
        p->iter = 0;
        return 1;
    }

    return 0;
}

int parse_delay(ftop_data *p, char *str)
{
    char *num_end;
    size_t len;
    uint32_t sec, hundredths;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_DELAY;

    sec = strtoul(str, &num_end, 10);
    hundredths = 0;
    if ((num_end != str) || ((num_end == str) && (*num_end == '.')))
    {
        if (*num_end == '.')
        {
            if ((len = strlen(num_end + 1)) > 2)
                return 0;

            hundredths = strtoul(num_end + 1, &num_end, 10);
            if (len == 1)
                hundredths *= 10;
        }

        if ((*num_end == '\0') && ((sec != 0) || (hundredths != 0)))
        {
            p->opt_delay_ms = (sec * 1000) + (hundredths * 10);
            return 1;
        }
    }

    return 0;
}

int parse_file(ftop_data *p, char *str)
{
    char *file_buf;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_FILE;

    if (str[0] == '\0')
    {
        free(p->opt_file);
        p->opt_file = NULL;
        return 1;
    }

    if (!parse_comma_list(str, &file_buf, NULL, NULL))
        return 0;

    p->opt_file = file_buf;
    return 1;
}

int parse_log(ftop_data *p, char *str)
{
    size_t i;
    int opt_log, none;
    char *log_buf, *next;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_LOG;

    if (!parse_comma_list(str, &log_buf, NULL, opt_log_strs))
        return 0;

    opt_log = 0;
    none = 0;
    for (next = log_buf; *next != '\0'; next += (strlen(next) + 1))
    {
        if (none == 1)      /* if "none" already specified, no more allowed */
            break;

        if (strcmp(next, "all") == 0)
        {
            if ((next != log_buf) || (next[4] != '\0'))
                break;

            opt_log = LOG_ALL;
            continue;
        }

        for (i = 0; opt_log_strs[i] != NULL; i++)
        {
            if (strcmp(next, opt_log_strs[i]) == 0)
            {
                if (i == 0)
                {
                    if (opt_log != 0)
                        break;

                    none = 1;
                }
                else if (opt_log & opt_log_vals[i])
                {
                    break;
                }

                opt_log |= opt_log_vals[i];
            }
        }

        if (opt_log_strs[i] != NULL)
            break;
    }

    if (*next == '\0')
    {
        free(log_buf);
        p->opt_log = opt_log;
        if (!none && !(p->opt_help) && !(p->opt_options) && !(p->opt_version))
        {
            p->opt_view_log = 1;
            p->scrolled_rows = 0;
        }
        else
        {
            p->opt_view_log = 0;
        }

        return 1;
    }

    if (none)
        p->opt_view_log = 0;

    free(log_buf);
    return 0;
}

int parse_mode(ftop_data *p, char *str)
{
    size_t i, j, idx;
    int opt_mode[3];
    char *mode_buf, *next;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_MODE;

    if (!parse_comma_list(str, &mode_buf, NULL, opt_mode_strs))
        return 0;

    idx = 0;
    for (next = mode_buf; *next != '\0'; next += (strlen(next) + 1))
    {
        if (strcmp(next, "all") == 0)
        {
            if ((next != mode_buf) || (next[4] != '\0'))
                break;

            opt_mode[idx++] = opt_mode_vals[0];
            opt_mode[idx++] = opt_mode_vals[1];
            opt_mode[idx++] = opt_mode_vals[2];
            continue;
        }

        for (i = 0; opt_mode_strs[i] != NULL; i++)
        {
            if (strcmp(next, opt_mode_strs[i]) == 0)
            {
                for (j = 0; j < idx; j++)
                {
                    if (opt_mode[j] == opt_mode_vals[i])
                        break;
                }

                if (j < idx)
                    break;

                opt_mode[idx++] = opt_mode_vals[i];
            }
        }

        if (opt_mode_strs[i] != NULL)
            break;
    }

    if (*next == '\0')
    {
        free(mode_buf);
        for (i = 0; i < 3; i++)
        {
            if (i < idx)
                p->opt_mode[i] = opt_mode[i];
            else
                p->opt_mode[i] = opt_mode[0];
        }

        return 1;
    }

    return 0;
}

int parse_iterations(ftop_data *p, char *str)
{
    char *num_end;
    uint32_t opt_iterations;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_ITERATIONS;

    opt_iterations = strtoul(str, &num_end, 10);
    if ((num_end != str) && (*num_end == '\0'))
    {
        p->opt_iterations = opt_iterations;
        p->iter = 0;
        return 1;
    }

    return 0;
}

int parse_procs(ftop_data *p, char *str)
{
    size_t i, num_keywords, num_pids;
    char *keyword_buf, *next, *num_end, *dest, *src;
    pid_t *pid_buf;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_PROCS;

    if (str[0] == '\0')
    {
        free(p->opt_procs_pid);
        free(p->opt_procs_keyword);
        p->opt_procs_pid = NULL;
        p->opt_procs_keyword = NULL;
        return 1;
    }

    if (!parse_comma_list(str, &keyword_buf, &num_keywords, NULL))
        return 0;

    if ((pid_buf = malloc((num_keywords + 1) * sizeof(*pid_buf))) == NULL)
    {
        free(keyword_buf);
        return 0;
    }

    num_pids = 0;
    next = keyword_buf;
    for (i = 0; i < num_keywords; i++)
    {
        pid_buf[num_pids] = strtoul(next, &num_end, 0);
        if ((num_end == next) || (*num_end != '\0'))
        {
            next += (strlen(next) + 1);
            continue;
        }

        src = next + strlen(next) + 1;
        dest = next;
        while ((*src != '\0') || (*(src - 1) != '\0'))
            *(dest++) = *(src++);

        *(dest++) = '\0';
        num_pids++;
        i--;
        num_keywords--;
    }

    pid_buf[num_pids] = 0;
    if (p->opt_procs_pid != NULL)
        free(p->opt_procs_pid);

    if (p->opt_procs_keyword != NULL)
        free(p->opt_procs_keyword);

    /* opt_procs_keyword and opt_proc_pid must both be valid or both be NULL */
    p->opt_procs_pid = pid_buf;
    p->opt_procs_keyword = keyword_buf;
    return 1;
}

int parse_type(ftop_data *p, char *str)
{
    size_t i, out_idx;
    char tmp_type[NUM_TYPES + 1];
    char *type_buf, *next;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_TYPE;

    if (!parse_comma_list(str, &type_buf, NULL, opt_type_strs))
        return 0;

    out_idx = 0;
    for (next = type_buf; *next != '\0'; next += (strlen(next) + 1))
    {
        if (strcmp(next, "all") == 0)
        {
            if ((next != type_buf) || (next[4] != '\0'))
                break;

            for (out_idx = 0; out_idx < NUM_TYPES; out_idx++)
                tmp_type[out_idx] = opt_type_strs[out_idx][0];

            continue;
        }

        if ((*next == 'r') && (*(next + 1) == '\0'))
            *next = 'f';

        for (i = 0; i < out_idx; i++)
        {
            if (tmp_type[i] == *next)
                break;
        }

        if (i < out_idx)
            break;

        tmp_type[out_idx++] = *next;
    }

    if (*next == '\0')
    {
        free(type_buf);
        tmp_type[out_idx++] = '\0';
        memcpy(p->opt_type, tmp_type, sizeof(p->opt_type));
        return 1;
    }

    free(type_buf);
    return 0;
}

int parse_uid(ftop_data *p, char *str)
{
    char *num_end;
    uid_t opt_uid;
    struct passwd *pw;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_UID;

    if (str[0] == '\0')
    {
        p->opt_uid_specified = 0;
        return 1;
    }

    opt_uid = strtoul(str, &num_end, 0);
    if ((num_end == str) || (*num_end != '\0'))
    {
        if ((pw = getpwnam(str)) == NULL)
            return 0;

        opt_uid = pw->pw_uid;
        add_lookup_user(p, pw);
    }

    p->opt_uid_specified = 1;
    p->opt_uid = opt_uid;
    return 1;
}

int parse_max_no_match(ftop_data *p, char *str)
{
    char *num_end;
    uint32_t opt_max_no_match;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_EXIT_ITERS;

    opt_max_no_match = strtoul(str, &num_end, 0);
    if ((num_end != str) && (*num_end == '\0'))
    {
        p->opt_max_no_match = opt_max_no_match;
        return 1;
    }

    return 0;
}

int parse_fstype(ftop_data *p, char *str)
{
    char *fstype_buf, *next;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_FSTYPE;

    if (str[0] == '\0')
    {
        free(p->opt_fstype);
        p->opt_fstype = NULL;
        mkstr_add(p);
        parse_add(p, p->input_buf);
        return 1;
    }

    if (!parse_comma_list(str, &fstype_buf, NULL, NULL))
        return 0;

    for (next = fstype_buf; *next != '\0'; next += (strlen(next) + 1))
    {
        if (strcmp(next, "all") == 0)
        {
            if ((next != fstype_buf) || (next[4] != '\0'))
            {
                free(fstype_buf);
                return 0;
            }
        }
    }

    p->opt_fstype = fstype_buf;
    if (p->mtab_size != 0)
    {
        free(p->mtab_buf);
        p->mtab_buf = NULL;
        p->mtab_size = 0;
    }

    return 1;
}

int parse_size(ftop_data *p, char *str)
{
    int invalid;
    size_t i, num_keywords;
    char *keyword_buf, *next, *num_end, *num, *dest, *src;
    off_t *val_buf;
    uint32_t fpart;
    double mult;

    if (str == NULL)
        return 0;

    if (str[0] == '\0')
        str = DEFAULT_SIZE;

    if (str[0] == '\0')
    {
        free(p->opt_size_val);
        free(p->opt_size_keyword);
        p->opt_size_val = NULL;
        p->opt_size_keyword = NULL;
        return 1;
    }

    if (!parse_comma_list(str, &keyword_buf, &num_keywords, NULL))
        return 0;

    if ((val_buf = malloc((num_keywords + 1) * sizeof(*val_buf))) == NULL)
    {
        free(keyword_buf);
        return 0;
    }

    next = keyword_buf;
    for (i = 0; i < num_keywords; i++)
    {
        if (((num = strrchr(next, '=')) == NULL) || (num == next))
        {
            free(val_buf);
            free(keyword_buf);
            return 0;
        }

        *(num++) = '\0';
        val_buf[i] = strtoull(num, &num_end, 10);
        invalid = 0;
        fpart = 0;
        if (*num_end == '.')
        {
            if ((fpart = strtoull(num_end + 1, &num_end, 10)) > 9)
                invalid = 1;
        }

        mult = val_buf[i] + (fpart * 0.1);
        if (strcmp(num_end, "M") == 0)
            val_buf[i] = (mult * 1048576) + 1;
        else if (strcmp(num_end, "G") == 0)
            val_buf[i] = (mult * 1024 * 1048576) + 1;
        else if (strcmp(num_end, "e6") == 0)
            val_buf[i] = (mult * 1000000) + 1;
        else if (strcmp(num_end, "e9") == 0)
            val_buf[i] = (mult * 1000000000) + 1;
        else if ((*num_end != '\0') || (fpart != 0))
            invalid = 1;

        if (val_buf[i] == 0)
            invalid = 1;

        if (invalid)
        {
            free(val_buf);
            free(keyword_buf);
            return 0;
        }

        while (*num_end != '\0')
            num_end++;

        src = num_end + 1;
        next = num;
        dest = next;
        while ((*src != '\0') || (*(src - 1) != '\0'))
            *(dest++) = *(src++);

        *(dest++) = '\0';
    }

    val_buf[num_keywords] = 0;
    if (p->opt_size_val != NULL)
        free(p->opt_size_val);

    if (p->opt_size_keyword != NULL)
        free(p->opt_size_keyword);

    /* opt_size_keyword and opt_size_val must both be valid or both be NULL */
    p->opt_size_val = val_buf;
    p->opt_size_keyword = keyword_buf;
    return 1;
}

int mkstr_add(ftop_data *p)
{
    size_t i, offset;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_add_info != NULL)
    {
        offset = 0;
        for (i = 0; p->opt_add_info[i].path != NULL; i++)
        {
            if (p->opt_add_info[i].flags & FLAG_FSTYPE)
                continue;

            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%s", ((offset == 0) ? "" : ","),
                               p->opt_add_info[i].path);
        }
    }

    return 1;
}

int mkstr_closed(ftop_data *p)
{
    snprintf(p->input_buf, MAX_INPUT_BUF, "%u", p->opt_closed);

    return 1;
}

int mkstr_delay(ftop_data *p)
{
    snprintf(p->input_buf, MAX_INPUT_BUF, "%u.%02u", p->opt_delay_ms / 1000,
             (p->opt_delay_ms % 1000) / 10);

    return 1;
}

int mkstr_file(ftop_data *p)
{
    char *next;
    size_t offset;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_file != NULL)
    {
        offset = 0;
        next = p->opt_file;
        while (*next != '\0')
        {
            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%s", ((next == p->opt_file) ? "" : ","),
                               next);

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

    return 1;
}

int mkstr_log(ftop_data *p)
{
    size_t i, offset;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_log == LOG_NONE)
    {
        snprintf(p->input_buf, MAX_INPUT_BUF, "none");
    }
    else if (p->opt_log == LOG_ALL)
    {
        snprintf(p->input_buf, MAX_INPUT_BUF, "all");
    }
    else
    {
        offset = 0;
        for (i = 0; strcmp(opt_log_strs[i], "all") != 0; i++)
        {
            if (p->opt_log & opt_log_vals[i])
            {
                offset += snprintf(p->input_buf + offset,
                                   MAX_INPUT_BUF - offset, "%s%s",
                                   ((offset == 0) ? "" : ","),
                                   opt_log_strs[i]);
            }
        }
    }

    return 1;
}

int mkstr_mode(ftop_data *p)
{
    size_t i, offset;

    offset = 0;
    memset(p->input_buf, 0, MAX_INPUT_BUF);
    for (i = 0; i < 3; i++)
    {
        if ((p->opt_mode[0] == opt_mode_vals[i]) ||
            (p->opt_mode[1] == opt_mode_vals[i]) ||
            (p->opt_mode[2] == opt_mode_vals[i]))
        {
            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%s", ((offset == 0) ? "" : ","),
                               opt_mode_strs[i]);
        }
    }

    return 1;
}

int mkstr_iterations(ftop_data *p)
{
    snprintf(p->input_buf, MAX_INPUT_BUF, "%u", p->opt_iterations);

    return 1;
}

int mkstr_procs(ftop_data *p)
{
    char *next;
    size_t i, offset;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_procs_pid != NULL)
    {
        offset = 0;
        next = p->opt_procs_keyword;
        while (*next != '\0')
        {
            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%s", ((offset == 0) ? "" : ","), next);

            next += strlen(next) + 1;
        }

        for (i = 0; p->opt_procs_pid[i] != 0; i++)
        {
            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%u", ((offset == 0) ? "" : ","),
                               p->opt_procs_pid[i]);
        }
    }

    return 1;
}

int mkstr_type(ftop_data *p)
{
    size_t i;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (strlen(p->opt_type) == NUM_TYPES)
    {
        snprintf(p->input_buf, MAX_INPUT_BUF, "all");
    }
    else
    {
        for (i = 0; p->opt_type[i] != '\0'; i++)
        {
            p->input_buf[i * 2] = p->opt_type[i];
            if (p->opt_type[i + 1] != '\0')
                p->input_buf[(i * 2) + 1] = ',';
        }
    }

    return 1;
}

int mkstr_uid(ftop_data *p)
{
    char *tmp_str;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_uid_specified)
    {
        if ((tmp_str = lookup_user(p, p->opt_uid)) != NULL)
            snprintf(p->input_buf, MAX_INPUT_BUF, "%s", tmp_str);
        else
            snprintf(p->input_buf, MAX_INPUT_BUF, "%u", p->opt_uid);
    }

    return 1;
}

int mkstr_max_no_match(ftop_data *p)
{
    snprintf(p->input_buf, MAX_INPUT_BUF, "%u", p->opt_max_no_match);

    return 1;
}

int mkstr_fstype(ftop_data *p)
{
    char *next;
    size_t offset;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_fstype != NULL)
    {
        offset = 0;
        next = p->opt_fstype;
        while (*next != '\0')
        {
            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%s", ((next == p->opt_fstype) ? "" : ","),
                               next);

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

    return 1;
}

int mkstr_size(ftop_data *p)
{
    char *next;
    char size_buf[32];
    size_t offset, idx;

    memset(p->input_buf, 0, MAX_INPUT_BUF);
    if (p->opt_size_keyword != NULL)
    {
        offset = 0;
        idx = 0;
        next = p->opt_size_keyword;
        while (*next != '\0')
        {
            label_value(p, size_buf, sizeof(size_buf), p->opt_size_val[idx++]);
            offset += snprintf(p->input_buf + offset, MAX_INPUT_BUF - offset,
                               "%s%s=%s", ((idx == 1) ? "" : ","), next,
                               size_buf);

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

    return 1;
}

int update_add(ftop_data *p)
{
    size_t i, j;
    file_info *file;
    char *next;
    struct statvfs fs;
    snapshot *s, *s_prev;

    if (p->opt_add_info == NULL)
        return 1;

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

    for (i = 0; p->opt_add_info[i].path != NULL; i++)
    {
        file = &(p->opt_add_info[i]);
        memcpy(file->prev, file, sizeof(*file));

        memset(&(file->st), 0, sizeof(file->st));
        if (stat(file->path, &(file->st)) < 0)
        {
            memset(&(file->st), 0, sizeof(file->st));
            continue;
        }

        if (S_ISDIR(file->st.st_mode) && (statvfs(file->path, &fs) == 0))
        {
            if (fs.f_frsize == 0)
                fs.f_frsize = fs.f_bsize;

            file->st.st_size = fs.f_blocks * fs.f_frsize;
            file->pos = (fs.f_blocks - fs.f_bfree) * fs.f_frsize;
            file->flags |= FLAG_FS;
        }
        else
        {
            file->pos = file->st.st_size;
            file->flags &= ~FLAG_FS;
        }

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

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

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

    qsort(p->opt_add_info, i, sizeof(*(p->opt_add_info)),
          compare_file_activity);

    for (i = 0; p->opt_add_info[i].path != NULL; i++)
    {
        file = &(p->opt_add_info[i]);
        file->prev->prev = file;
        calc_rate(file, &(s_prev->tv), &(s->tv));
    }

    return 1;
}

int update_fstype(ftop_data *p)
{
    int fd;
    struct stat st;
    ssize_t bytes, total;
    char *next, *newline, *path, *type, *sep1, *sep2;

    if (p->opt_fstype == NULL)
        return 1;

    if ((fd = open("/etc/mtab", O_RDONLY)) < 0)
        return 1;

    if (fstat(fd, &st) < 0)
    {
        close(fd);
        return 1;
    }

    if (st.st_size != p->mtab_size)
    {
        if (p->mtab_buf != NULL)
            free(p->mtab_buf);

        p->mtab_size = 0;
        if ((p->mtab_buf = malloc(st.st_size + 1)) == NULL)
        {
            close(fd);
            return 1;
        }

        p->mtab_size = st.st_size;
        memset(p->mtab_buf, 0, p->mtab_size + 1);
    }

    total = 0;
    while (total < st.st_size)
    {
        bytes = read(fd, p->scratch_buf + total, st.st_size - total);
        if (bytes < 0)
        {
            close(fd);
            return 1;
        }

        total += bytes;
    }

    close(fd);
    if (memcmp(p->scratch_buf, p->mtab_buf, st.st_size) == 0)
        return 1;

    p->mtab_size = 0;       /* temporary: so parse_add doesn't mess with us */
    memcpy(p->mtab_buf, p->scratch_buf, st.st_size);
    memcpy(p->scratch_buf, p->input_buf, MAX_INPUT_BUF);
    mkstr_add(p);
    total = strlen(p->input_buf);

    for (next = p->mtab_buf; (next != NULL) && (*next != '\0'); next = newline)
    {
        if ((newline = strchr(next, '\n')) != NULL)
            *newline = '\0';

        while ((*next != '\0') && (*next != ' '))
            next++;

        path = ++next;
        while ((*next != '\0') && (*next != ' '))
            next++;

        sep1 = next;
        *(next++) = '\0';
        type = next;
        while ((*next != '\0') && (*next != ' '))
            next++;

        sep2 = next;
        *next = '\0';
        next = p->opt_fstype;
        if (strcmp(p->opt_fstype, "all") != 0)
        {
            while (*next != '\0')
            {
                if (strcmp(next, type) == 0)
                    break;

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

        if (newline != NULL)
            *(newline++) = '\n';

        if (*next != '\0')
        {
            /* prefix the path with an extra '/': indicates added by fstype */
            total += snprintf(p->input_buf + total, MAX_INPUT_BUF - total,
                              "%s/%s", ((total == 0) ? "" : ","), path);
        }

        *sep1 = ' ';
        *sep2 = ' ';
    }

    parse_add(p, p->input_buf);
    memcpy(p->input_buf, p->scratch_buf, MAX_INPUT_BUF);
    p->mtab_size = st.st_size;      /* now it's ok to set this */

    return 1;
}

int show_option(ftop_data *p, char c, char *str, mkstr_function f,
                int part)
{
    p_printf(p, "  ");
    p_attron(p, A_BOLD);
    p_printf(p, "%c", c);
    p_attroff(p, A_BOLD);

    p_printf(p, "  %s ", str);
    p_repeat(p, (p->term_size.ws_col / 2) - p->cur_col - 1, 1, '_');

    f(p);
    if (p->input_buf[0] == '\0')
        p_printf(p, " (empty)");
    else
        p_printf(p, " %s", p->input_buf);

    p_eol(p, part);
    return 1;
}

int show_bool(ftop_data *p, char c, char *str, int enabled, int part)
{
    int cur_col, repeat_len;

    cur_col = p->cur_col;
    p_printf(p, "  ");
    p_attron(p, A_BOLD);
    p_printf(p, "%c", c);
    p_attroff(p, A_BOLD);

    p_printf(p, "  %s ", str);
    if (cur_col == 0)
        repeat_len = (p->term_size.ws_col / 2) - p->cur_col - 11;
    else
        repeat_len = p->term_size.ws_col - p->cur_col - 11;

    p_repeat(p, repeat_len, 1, '_');
    p_printf(p, " %-8s  ", (enabled ? "enabled" : "disabled"));

    return 1;
}

int show_options(ftop_data *p, int part)
{
    memcpy(p->scratch_buf, p->input_buf, MAX_INPUT_BUF);
    screen_title(p, "OPTIONS", OPT_OPTIONS_CHAR);

    show_option(p, OPT_ADD_CHAR, OPT_ADD_STR, mkstr_add, part);
    show_option(p, OPT_CLOSED_CHAR, OPT_CLOSED_STR, mkstr_closed, part);
    show_option(p, OPT_DELAY_CHAR, OPT_DELAY_STR, mkstr_delay, part);
    show_option(p, OPT_FILE_CHAR, OPT_FILE_STR, mkstr_file, part);
    show_option(p, OPT_LOG_CHAR, OPT_LOG_STR, mkstr_log, part);
    show_option(p, OPT_MODE_CHAR, OPT_MODE_STR, mkstr_mode, part);
    show_option(p, OPT_ITERATIONS_CHAR, OPT_ITERATIONS_STR, mkstr_iterations,
                part);

    show_option(p, OPT_PROCS_CHAR, OPT_PROCS_STR, mkstr_procs, part);
    show_option(p, OPT_TYPE_CHAR, OPT_TYPE_STR, mkstr_type, part);
    show_option(p, OPT_UID_CHAR, OPT_UID_STR, mkstr_uid, part);
    show_option(p, OPT_MAX_NO_MATCH_CHAR, OPT_MAX_NO_MATCH_STR,
                mkstr_max_no_match, part);

    show_option(p, OPT_FSTYPE_CHAR, OPT_FSTYPE_STR, mkstr_fstype, part);
    show_option(p, OPT_SIZE_CHAR, OPT_SIZE_STR, mkstr_size, part);

    p_eol(p, part);

    show_bool(p, OPT_ADD_ONLY_CHAR, OPT_ADD_ONLY_STR, p->opt_add_only, part);
    show_bool(p, OPT_FD_NUMBERS_CHAR, OPT_FD_NUMBERS_STR, p->opt_fd_numbers,
              part);

    p_eol(p, part);
    show_bool(p, OPT_EXPAND_ATBEGIN_CHAR, OPT_EXPAND_ATBEGIN_STR,
              p->opt_expand_atbegin, part);

    show_bool(p, OPT_SWITCH_OUTPUT_CHAR, OPT_SWITCH_OUTPUT_STR,
              (p->mode == MODE_LIMITED), part);

    p_eol(p, part);
    show_bool(p, OPT_HIDE_DIR_CHAR, OPT_HIDE_DIR_STR, p->opt_hide_dir, part);
    show_bool(p, OPT_UNEXP_PROCS_CHAR, OPT_UNEXP_PROCS_STR,
              p->opt_unexp_procs, part);

    p_eol(p, part);
    show_bool(p, OPT_EXPAND_ATEND_CHAR, OPT_EXPAND_ATEND_STR,
              p->opt_expand_atend, part);

    show_bool(p, OPT_QUIET_CHAR, OPT_QUIET_STR, p->opt_quiet, part);
    p_eol(p, part);
    show_bool(p, OPT_UNEXP_FILES_CHAR, OPT_UNEXP_FILES_STR,
              p->opt_unexp_files, part);

    show_bool(p, OPT_UNITS_BASE10_CHAR, OPT_UNITS_BASE10_STR,
              p->opt_units_base10, part);

    p_eol(p, part);

    memcpy(p->input_buf, p->scratch_buf, MAX_INPUT_BUF);
    return 1;
}
