/*
   Chown-advanced command -- for the Midnight Commander

   Copyright (C) 1994-2024
   Free Software Foundation, Inc.

   This file is part of the Midnight Commander.

   The Midnight Commander 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.

   The Midnight Commander 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/>.
 */

/** \file achown.c
 *  \brief Source: Contains functions for advanced chowning
 */

#include <config.h>

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "lib/global.h"

#include "lib/tty/tty.h"
#include "lib/tty/key.h"        /* XCTRL and ALT macros */
#include "lib/skin.h"
#include "lib/vfs/vfs.h"
#include "lib/strutil.h"
#include "lib/util.h"
#include "lib/widget.h"

#include "cmd.h"                /* advanced_chown_cmd() */

/*** global variables ****************************************************************************/

/*** file scope macro definitions ****************************************************************/

#define BX 5
#define BY 5

#define BUTTONS      9
#define BUTTONS_PERM 5

#define B_SETALL B_USER
#define B_SKIP   (B_USER + 1)

/*** file scope type declarations ****************************************************************/

/*** forward declarations (file scope functions) *************************************************/

/*** file scope variables ************************************************************************/

static struct
{
    unsigned long id;
    int ret_cmd;
    button_flags_t flags;
    int x;
    int len;
    const char *text;
} advanced_chown_but[BUTTONS] =
{
    /* *INDENT-OFF* */
    { 0, B_ENTER,   NARROW_BUTTON,  3, 0, "   " },
    { 0, B_ENTER,   NARROW_BUTTON, 11, 0, "   " },
    { 0, B_ENTER,   NARROW_BUTTON, 19, 0, "   " },
    { 0, B_ENTER,   NARROW_BUTTON, 29, 0, ""    },
    { 0, B_ENTER,   NARROW_BUTTON, 47, 0, ""    },

    { 0, B_SETALL,  NORMAL_BUTTON,  0, 0, N_("Set &all") },
    { 0, B_SKIP,    NORMAL_BUTTON,  0, 0, N_("S&kip")    },
    { 0, B_ENTER,  DEFPUSH_BUTTON,  0, 0, N_("&Set")     },
    { 0, B_CANCEL,  NORMAL_BUTTON,  0, 0, N_("&Cancel")  }
    /* *INDENT-ON* */
};

static int current_file;
static gboolean ignore_all;

static WButton *b_att[3];       /* permission */
static WButton *b_user, *b_group;       /* owner */
static WLabel *l_filename;
static WLabel *l_mode;

static int flag_pos;
static int x_toggle;
static char ch_flags[11];
static const char ch_perm[] = "rwx";
static mode_t ch_cmode;
static struct stat sf_stat;

/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */

static void
advanced_chown_init (void)
{
    static gboolean i18n = FALSE;
    int i;

    if (i18n)
        return;

    i18n = TRUE;

    for (i = BUTTONS_PERM; i < BUTTONS; i++)
    {
#ifdef ENABLE_NLS
        advanced_chown_but[i].text = _(advanced_chown_but[i].text);
#endif /* ENABLE_NLS */

        advanced_chown_but[i].len = str_term_width1 (advanced_chown_but[i].text) + 3;
        if (advanced_chown_but[i].flags == DEFPUSH_BUTTON)
            advanced_chown_but[i].len += 2;     /* "<>" */
    }

}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
inc_flag_pos (void)
{
    if (flag_pos == 10)
    {
        flag_pos = 0;
        return MSG_NOT_HANDLED;
    }

    flag_pos++;

    return flag_pos % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED;
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
dec_flag_pos (void)
{
    if (flag_pos == 0)
    {
        flag_pos = 10;
        return MSG_NOT_HANDLED;
    }

    flag_pos--;

    return (flag_pos + 1) % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED;
}

/* --------------------------------------------------------------------------------------------- */

static void
set_perm_by_flags (char *s, int f_p)
{
    int i;

    for (i = 0; i < 3; i++)
    {
        if (ch_flags[f_p + i] == '+')
            s[i] = ch_perm[i];
        else if (ch_flags[f_p + i] == '-')
            s[i] = '-';
        else
            s[i] = (ch_cmode & (1 << (8 - f_p - i))) != 0 ? ch_perm[i] : '-';
    }
}

/* --------------------------------------------------------------------------------------------- */

static mode_t
get_perm (char *s, int base)
{
    mode_t m = 0;

    m |= (s[0] == '-') ? 0 :
        ((s[0] == '+') ? (mode_t) (1 << (base + 2)) : (1 << (base + 2)) & ch_cmode);

    m |= (s[1] == '-') ? 0 :
        ((s[1] == '+') ? (mode_t) (1 << (base + 1)) : (1 << (base + 1)) & ch_cmode);

    m |= (s[2] == '-') ? 0 : ((s[2] == '+') ? (mode_t) (1 << base) : (1 << base) & ch_cmode);

    return m;
}

/* --------------------------------------------------------------------------------------------- */

static mode_t
get_mode (void)
{
    mode_t m;

    m = ch_cmode ^ (ch_cmode & 0777);
    m |= get_perm (ch_flags, 6);
    m |= get_perm (ch_flags + 3, 3);
    m |= get_perm (ch_flags + 6, 0);

    return m;
}

/* --------------------------------------------------------------------------------------------- */

static void
update_permissions (void)
{
    set_perm_by_flags (b_att[0]->text.start, 0);
    set_perm_by_flags (b_att[1]->text.start, 3);
    set_perm_by_flags (b_att[2]->text.start, 6);
}

/* --------------------------------------------------------------------------------------------- */

static void
update_ownership (void)
{
    button_set_text (b_user, get_owner (sf_stat.st_uid));
    button_set_text (b_group, get_group (sf_stat.st_gid));
}

/* --------------------------------------------------------------------------------------------- */

static void
print_flags (const WDialog * h)
{
    int i;

    tty_setcolor (COLOR_NORMAL);

    for (i = 0; i < 3; i++)
    {
        widget_gotoyx (h, BY + 1, advanced_chown_but[0].x + 6 + i);
        tty_print_char (ch_flags[i]);
    }

    for (i = 0; i < 3; i++)
    {
        widget_gotoyx (h, BY + 1, advanced_chown_but[1].x + 6 + i);
        tty_print_char (ch_flags[i + 3]);
    }

    for (i = 0; i < 3; i++)
    {
        widget_gotoyx (h, BY + 1, advanced_chown_but[2].x + 6 + i);
        tty_print_char (ch_flags[i + 6]);
    }

    update_permissions ();

    for (i = 0; i < 15; i++)
    {
        widget_gotoyx (h, BY + 1, advanced_chown_but[3].x + 6 + i);
        tty_print_char (ch_flags[9]);
    }
    for (i = 0; i < 15; i++)
    {
        widget_gotoyx (h, BY + 1, advanced_chown_but[4].x + 6 + i);
        tty_print_char (ch_flags[10]);
    }
}

/* --------------------------------------------------------------------------------------------- */

static void
advanced_chown_refresh (const WDialog * h)
{
    tty_setcolor (COLOR_NORMAL);

    widget_gotoyx (h, BY - 1, advanced_chown_but[0].x + 5);
    tty_print_string (_("owner"));
    widget_gotoyx (h, BY - 1, advanced_chown_but[1].x + 5);
    tty_print_string (_("group"));
    widget_gotoyx (h, BY - 1, advanced_chown_but[2].x + 5);
    tty_print_string (_("other"));

    widget_gotoyx (h, BY - 1, advanced_chown_but[3].x + 5);
    tty_print_string (_("owner"));
    widget_gotoyx (h, BY - 1, advanced_chown_but[4].x + 5);
    tty_print_string (_("group"));

    widget_gotoyx (h, BY + 1, 3);
    tty_print_string (_("Flag"));
    print_flags (h);
}

/* --------------------------------------------------------------------------------------------- */

static void
advanced_chown_info_update (void)
{
    /* mode */
    label_set_textv (l_mode, _("Permissions (octal): %o"), get_mode ());

    /* permissions */
    update_permissions ();
}

/* --------------------------------------------------------------------------------------------- */

static void
update_mode (WGroup * g)
{
    print_flags (DIALOG (g));
    advanced_chown_info_update ();
    widget_set_state (WIDGET (g->current->data), WST_FOCUSED, TRUE);
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
perm_button_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    WButton *b = BUTTON (w);
    WGroup *g = w->owner;
    int i = 0;
    int f_pos;

    /* one of permission buttons */
    if (b == b_att[0])
        f_pos = 0;
    else if (b == b_att[1])
        f_pos = 1;
    else                        /* if (w == b_att [1] */
        f_pos = 2;

    switch (msg)
    {
    case MSG_FOCUS:
        if (b->hotpos == -1)
            b->hotpos = 0;

        flag_pos = f_pos * 3 + b->hotpos;
        return MSG_HANDLED;

    case MSG_KEY:
        switch (parm)
        {
        case '*':
            parm = '=';
            MC_FALLTHROUGH;

        case '-':
        case '=':
        case '+':
            flag_pos = f_pos * 3 + b->hotpos;
            ch_flags[flag_pos] = parm;
            update_mode (g);
            send_message (w, NULL, MSG_KEY, KEY_RIGHT, NULL);
            if (b->hotpos == 2)
                group_select_next_widget (g);
            break;

        case XCTRL ('f'):
        case KEY_RIGHT:
            {
                cb_ret_t ret;

                ret = inc_flag_pos ();
                b->hotpos = flag_pos % 3;
                return ret;
            }

        case XCTRL ('b'):
        case KEY_LEFT:
            {
                cb_ret_t ret;

                ret = dec_flag_pos ();
                b->hotpos = flag_pos % 3;
                return ret;
            }

        case 'x':
            i++;
            MC_FALLTHROUGH;

        case 'w':
            i++;
            MC_FALLTHROUGH;

        case 'r':
            b->hotpos = i;
            MC_FALLTHROUGH;

        case ' ':
            i = b->hotpos;

            flag_pos = f_pos * 3 + i;
            if (b->text.start[flag_pos % 3] == '-')
                ch_flags[flag_pos] = '+';
            else
                ch_flags[flag_pos] = '-';
            update_mode (w->owner);
            break;

        case '4':
            i++;
            MC_FALLTHROUGH;

        case '2':
            i++;
            MC_FALLTHROUGH;

        case '1':
            b->hotpos = i;
            flag_pos = f_pos * 3 + i;
            ch_flags[flag_pos] = '=';
            update_mode (g);
            break;

        default:
            break;
        }
        /* continue key handling in the dialog level */
        return MSG_NOT_HANDLED;

    default:
        return button_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static void
perm_button_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
{
    switch (msg)
    {
    case MSG_MOUSE_DOWN:
        /* place cursor on flag that is being modified */
        BUTTON (w)->hotpos = CLAMP (event->x - 1, 0, 2);
        MC_FALLTHROUGH;

    default:
        button_mouse_default_callback (w, msg, event);
        break;
    }
}

/* --------------------------------------------------------------------------------------------- */

static WButton *
perm_button_new (int y, int x, int action, button_flags_t flags, const char *text,
                 bcback_fn callback)
{
    WButton *b;
    Widget *w;

    /* create base button using native API */
    b = button_new (y, x, action, flags, text, callback);
    w = WIDGET (b);

    /* we don't want HOTKEY */
    widget_want_hotkey (w, FALSE);

    w->callback = perm_button_callback;
    w->mouse_callback = perm_button_mouse_callback;

    return b;
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
chl_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_KEY:
        switch (parm)
        {
        case KEY_LEFT:
        case KEY_RIGHT:
            {
                WDialog *h = DIALOG (w);

                h->ret_value = parm;
                dlg_close (h);
            }
            break;
        default:
            break;
        }
        MC_FALLTHROUGH;

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static int
user_group_button_cb (WButton * button, int action)
{
    Widget *w = WIDGET (button);
    int f_pos;
    gboolean chl_end;

    (void) action;

    if (button == b_user)
        f_pos = BUTTONS_PERM - 2;
    else if (button == b_group)
        f_pos = BUTTONS_PERM - 1;
    else
        return 0;               /* do nothing */

    do
    {
        WGroup *g = w->owner;
        WDialog *h = DIALOG (g);
        Widget *wh = WIDGET (h);

        gboolean is_owner = (f_pos == BUTTONS_PERM - 2);
        const char *title;
        int lxx, b_current;
        WDialog *chl_dlg;
        WListbox *chl_list;
        int result;
        int fe;
        struct passwd *chl_pass;
        struct group *chl_grp;

        chl_end = FALSE;

        if (is_owner)
        {
            title = _("owner");
            lxx = WIDGET (b_user)->rect.x + 1;
        }
        else
        {
            title = _("group");
            lxx = WIDGET (b_group)->rect.x + 1;
        }

        chl_dlg =
            dlg_create (TRUE, wh->rect.y - 1, lxx, wh->rect.lines + 2, 17, WPOS_KEEP_DEFAULT, TRUE,
                        dialog_colors, chl_callback, NULL, "[Advanced Chown]", title);

        /* get new listboxes */
        chl_list =
            listbox_new (1, 1, WIDGET (chl_dlg)->rect.lines - 2, WIDGET (chl_dlg)->rect.cols - 2,
                         FALSE, NULL);
        listbox_add_item (chl_list, LISTBOX_APPEND_AT_END, 0, "<Unknown>", NULL, FALSE);
        if (is_owner)
        {
            /* get and put user names in the listbox */
            setpwent ();
            while ((chl_pass = getpwent ()) != NULL)
                listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_pass->pw_name, NULL,
                                  FALSE);
            endpwent ();
            fe = listbox_search_text (chl_list, get_owner (sf_stat.st_uid));
        }
        else
        {
            /* get and put group names in the listbox */
            setgrent ();
            while ((chl_grp = getgrent ()) != NULL)
                listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_grp->gr_name, NULL,
                                  FALSE);
            endgrent ();
            fe = listbox_search_text (chl_list, get_group (sf_stat.st_gid));
        }

        listbox_set_current (chl_list, fe);

        b_current = chl_list->current;
        group_add_widget (GROUP (chl_dlg), chl_list);

        result = dlg_run (chl_dlg);

        if (result != B_CANCEL)
        {
            if (b_current != chl_list->current)
            {
                gboolean ok = FALSE;
                char *text;

                listbox_get_current (chl_list, &text, NULL);
                if (is_owner)
                {
                    chl_pass = getpwnam (text);
                    if (chl_pass != NULL)
                    {
                        sf_stat.st_uid = chl_pass->pw_uid;
                        ok = TRUE;
                    }
                }
                else
                {
                    chl_grp = getgrnam (text);
                    if (chl_grp != NULL)
                    {
                        sf_stat.st_gid = chl_grp->gr_gid;
                        ok = TRUE;
                    }
                }

                if (!ok)
                    group_select_current_widget (g);
                else
                {
                    ch_flags[f_pos + 6] = '+';
                    update_ownership ();
                    group_select_current_widget (g);
                    print_flags (h);
                }
            }

            if (result == KEY_LEFT)
            {
                if (!is_owner)
                    chl_end = TRUE;
                group_select_prev_widget (g);
                f_pos--;
            }
            else if (result == KEY_RIGHT)
            {
                if (is_owner)
                    chl_end = TRUE;
                group_select_next_widget (g);
                f_pos++;
            }
        }

        /* Here we used to redraw the window */
        widget_destroy (WIDGET (chl_dlg));
    }
    while (chl_end);

    return 0;
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
advanced_chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    switch (msg)
    {
    case MSG_DRAW:
        frame_callback (w, NULL, MSG_DRAW, 0, NULL);
        advanced_chown_refresh (DIALOG (w->owner));
        advanced_chown_info_update ();
        return MSG_HANDLED;

    default:
        return frame_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static cb_ret_t
advanced_chown_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
    WGroup *g = GROUP (w);
    int i = 0;

    switch (msg)
    {
    case MSG_KEY:
        switch (parm)
        {
        case ALT ('x'):
            i++;
            MC_FALLTHROUGH;

        case ALT ('w'):
            i++;
            MC_FALLTHROUGH;

        case ALT ('r'):
            parm = i + 3;
            for (i = 0; i < 3; i++)
                ch_flags[i * 3 + parm - 3] = (x_toggle & (1 << parm)) ? '-' : '+';
            x_toggle ^= (1 << parm);
            update_mode (g);
            widget_draw (w);
            break;

        case XCTRL ('x'):
            i++;
            MC_FALLTHROUGH;

        case XCTRL ('w'):
            i++;
            MC_FALLTHROUGH;

        case XCTRL ('r'):
            parm = i;
            for (i = 0; i < 3; i++)
                ch_flags[i * 3 + parm] = (x_toggle & (1 << parm)) ? '-' : '+';
            x_toggle ^= (1 << parm);
            update_mode (g);
            widget_draw (w);
            break;

        default:
            break;
        }
        return MSG_NOT_HANDLED;

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

/* --------------------------------------------------------------------------------------------- */

static WDialog *
advanced_chown_dlg_create (WPanel * panel)
{
    gboolean single_set;
    WDialog *ch_dlg;
    WGroup *ch_grp;
    int lines = 12;
    int cols = 74;
    int i;
    int y;

    memset (ch_flags, '=', 11);
    flag_pos = 0;
    x_toggle = 070;

    single_set = (panel->marked < 2);
    if (!single_set)
        lines += 2;

    ch_dlg =
        dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
                    advanced_chown_callback, NULL, "[Advanced Chown]", _("Chown advanced command"));
    ch_grp = GROUP (ch_dlg);

    /* draw background */
    ch_dlg->bg->callback = advanced_chown_bg_callback;

    l_filename = label_new (2, 3, NULL);
    group_add_widget (ch_grp, l_filename);

    group_add_widget (ch_grp, hline_new (3, -1, -1));

#define XTRACT(i,y,cb) y, BX+advanced_chown_but[i].x, \
        advanced_chown_but[i].ret_cmd, advanced_chown_but[i].flags, \
        (advanced_chown_but[i].text), cb
    b_att[0] = perm_button_new (XTRACT (0, BY, NULL));
    advanced_chown_but[0].id = group_add_widget (ch_grp, b_att[0]);
    b_att[1] = perm_button_new (XTRACT (1, BY, NULL));
    advanced_chown_but[1].id = group_add_widget (ch_grp, b_att[1]);
    b_att[2] = perm_button_new (XTRACT (2, BY, NULL));
    advanced_chown_but[2].id = group_add_widget (ch_grp, b_att[2]);
    b_user = button_new (XTRACT (3, BY, user_group_button_cb));
    advanced_chown_but[3].id = group_add_widget (ch_grp, b_user);
    b_group = button_new (XTRACT (4, BY, user_group_button_cb));
    advanced_chown_but[4].id = group_add_widget (ch_grp, b_group);

    l_mode = label_new (BY + 2, 3, NULL);
    group_add_widget (ch_grp, l_mode);

    y = BY + 3;
    if (!single_set)
    {
        i = BUTTONS_PERM;
        group_add_widget (ch_grp, hline_new (y++, -1, -1));
        advanced_chown_but[i].id = group_add_widget (ch_grp,
                                                     button_new (y,
                                                                 WIDGET (ch_dlg)->rect.cols / 2 -
                                                                 advanced_chown_but[i].len,
                                                                 advanced_chown_but[i].ret_cmd,
                                                                 advanced_chown_but[i].flags,
                                                                 advanced_chown_but[i].text, NULL));
        i++;
        advanced_chown_but[i].id = group_add_widget (ch_grp,
                                                     button_new (y,
                                                                 WIDGET (ch_dlg)->rect.cols / 2 + 1,
                                                                 advanced_chown_but[i].ret_cmd,
                                                                 advanced_chown_but[i].flags,
                                                                 advanced_chown_but[i].text, NULL));
        y++;
    }

    i = BUTTONS_PERM + 2;
    group_add_widget (ch_grp, hline_new (y++, -1, -1));
    advanced_chown_but[i].id = group_add_widget (ch_grp,
                                                 button_new (y,
                                                             WIDGET (ch_dlg)->rect.cols / 2 -
                                                             advanced_chown_but[i].len,
                                                             advanced_chown_but[i].ret_cmd,
                                                             advanced_chown_but[i].flags,
                                                             advanced_chown_but[i].text, NULL));
    i++;
    advanced_chown_but[i].id = group_add_widget (ch_grp,
                                                 button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
                                                             advanced_chown_but[i].ret_cmd,
                                                             advanced_chown_but[i].flags,
                                                             advanced_chown_but[i].text, NULL));

    widget_select (WIDGET (b_att[0]));

    return ch_dlg;
}

/* --------------------------------------------------------------------------------------------- */

static void
advanced_chown_done (gboolean need_update)
{
    if (need_update)
        update_panels (UP_OPTIMIZE, UP_KEEPSEL);
    repaint_screen ();
}

/* --------------------------------------------------------------------------------------------- */

static const GString *
next_file (const WPanel * panel)
{
    while (panel->dir.list[current_file].f.marked == 0)
        current_file++;

    return panel->dir.list[current_file].fname;
}

/* --------------------------------------------------------------------------------------------- */

static gboolean
try_advanced_chown (const vfs_path_t * p, mode_t m, uid_t u, gid_t g)
{
    int chmod_result;
    const char *fname = NULL;

    while ((chmod_result = mc_chmod (p, m)) == -1 && !ignore_all)
    {
        int my_errno = errno;
        int result;
        char *msg;

        if (fname == NULL)
            fname = x_basename (vfs_path_as_str (p));
        msg = g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname, unix_error_string (my_errno));
        result =
            query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
                          _("&Cancel"));
        g_free (msg);

        switch (result)
        {
        case 0:
            /* call mc_chown() only, if mc_chmod() didn't fail */
            return TRUE;

        case 1:
            ignore_all = TRUE;
            /* call mc_chown() only, if mc_chmod() didn't fail */
            return TRUE;

        case 2:
            /* retry chmod of this file */
            break;

        case 3:
        default:
            /* stop remain files processing */
            return FALSE;
        }
    }

    /* call mc_chown() only, if mc_chmod didn't fail */
    while (chmod_result != -1 && mc_chown (p, u, g) == -1 && !ignore_all)
    {
        int my_errno = errno;
        int result;
        char *msg;

        if (fname == NULL)
            fname = x_basename (vfs_path_as_str (p));
        msg = g_strdup_printf (_("Cannot chown \"%s\"\n%s"), fname, unix_error_string (my_errno));
        result =
            query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
                          _("&Cancel"));
        g_free (msg);

        switch (result)
        {
        case 0:
            /* try next file */
            return TRUE;

        case 1:
            ignore_all = TRUE;
            /* try next file */
            return TRUE;

        case 2:
            /* retry chown of this file */
            break;

        case 3:
        default:
            /* stop remain files processing */
            return FALSE;
        }
    }

    return TRUE;

}

/* --------------------------------------------------------------------------------------------- */

static gboolean
do_advanced_chown (WPanel * panel, const vfs_path_t * p, mode_t m, uid_t u, gid_t g)
{
    gboolean ret;

    ret = try_advanced_chown (p, m, u, g);

    do_file_mark (panel, current_file, 0);

    return ret;
}

 /* --------------------------------------------------------------------------------------------- */

static void
apply_advanced_chowns (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
{
    gid_t a_gid = sf->st_gid;
    uid_t a_uid = sf->st_uid;
    gboolean ok;

    if (!do_advanced_chown (panel, vpath, get_mode (),
                            (ch_flags[9] == '+') ? a_uid : (uid_t) (-1),
                            (ch_flags[10] == '+') ? a_gid : (gid_t) (-1)))
        return;

    do
    {
        const GString *fname;

        fname = next_file (panel);
        vpath = vfs_path_from_str (fname->str);
        ok = (mc_stat (vpath, sf) == 0);

        if (!ok)
        {
            /* if current file was deleted outside mc -- try next file */
            /* decrease panel->marked */
            do_file_mark (panel, current_file, 0);

            /* try next file */
            ok = TRUE;
        }
        else
        {
            ch_cmode = sf->st_mode;

            ok = do_advanced_chown (panel, vpath, get_mode (),
                                    (ch_flags[9] == '+') ? a_uid : (uid_t) (-1),
                                    (ch_flags[10] == '+') ? a_gid : (gid_t) (-1));
        }

        vfs_path_free (vpath, TRUE);
    }
    while (ok && panel->marked != 0);
}

/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */

void
advanced_chown_cmd (WPanel * panel)
{
    gboolean need_update;
    gboolean end_chown;

    /* Number of files at startup */
    int files_on_begin;

    files_on_begin = MAX (1, panel->marked);

    advanced_chown_init ();

    current_file = 0;
    ignore_all = FALSE;

    do
    {                           /* do while any files remaining */
        vfs_path_t *vpath;
        WDialog *ch_dlg;
        const GString *fname;
        int result;
        int file_idx;

        do_refresh ();

        need_update = FALSE;
        end_chown = FALSE;

        if (panel->marked != 0)
            fname = next_file (panel);  /* next marked file */
        else
            fname = panel_current_entry (panel)->fname; /* single file */

        vpath = vfs_path_from_str (fname->str);

        if (mc_stat (vpath, &sf_stat) != 0)
        {
            vfs_path_free (vpath, TRUE);
            break;
        }

        ch_cmode = sf_stat.st_mode;

        ch_dlg = advanced_chown_dlg_create (panel);

        file_idx = files_on_begin == 1 ? 1 : (files_on_begin - panel->marked + 1);
        label_set_textv (l_filename, "%s (%d/%d)",
                         str_fit_to_term (fname->str, WIDGET (ch_dlg)->rect.cols - 20, J_LEFT_FIT),
                         file_idx, files_on_begin);
        update_ownership ();

        result = dlg_run (ch_dlg);

        switch (result)
        {
        case B_CANCEL:
            end_chown = TRUE;
            break;

        case B_ENTER:
            {
                uid_t uid = ch_flags[9] == '+' ? sf_stat.st_uid : (uid_t) (-1);
                gid_t gid = ch_flags[10] == '+' ? sf_stat.st_gid : (gid_t) (-1);

                if (panel->marked <= 1)
                {
                    /* single or last file */
                    if (mc_chmod (vpath, get_mode ()) == -1)
                        message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"),
                                 fname->str, unix_error_string (errno));
                    /* call mc_chown only, if mc_chmod didn't fail */
                    else if (mc_chown (vpath, uid, gid) == -1)
                        message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"), fname->str,
                                 unix_error_string (errno));

                    end_chown = TRUE;
                }
                else if (!try_advanced_chown (vpath, get_mode (), uid, gid))
                {
                    /* stop multiple files processing */
                    result = B_CANCEL;
                    end_chown = TRUE;
                }

                need_update = TRUE;
                break;
            }

        case B_SETALL:
            apply_advanced_chowns (panel, vpath, &sf_stat);
            need_update = TRUE;
            end_chown = TRUE;
            break;

        case B_SKIP:
        default:
            break;
        }

        if (panel->marked != 0 && result != B_CANCEL)
        {
            do_file_mark (panel, current_file, 0);
            need_update = TRUE;
        }

        vfs_path_free (vpath, TRUE);

        widget_destroy (WIDGET (ch_dlg));
    }
    while (panel->marked != 0 && !end_chown);

    advanced_chown_done (need_update);
}

/* --------------------------------------------------------------------------------------------- */
