/*
Chmod 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 .
*/
/** \file chmod.c
* \brief Source: chmod command
*/
#include
#include
#include
#include
#include
#include "lib/global.h"
#include "lib/tty/tty.h"
#include "lib/skin.h"
#include "lib/vfs/vfs.h"
#include "lib/strutil.h"
#include "lib/util.h"
#include "lib/widget.h"
#include "cmd.h" /* chmod_cmd() */
/*** global variables ****************************************************************************/
/*** file scope macro definitions ****************************************************************/
#define PX 3
#define PY 2
#define B_MARKED B_USER
#define B_SETALL (B_USER + 1)
#define B_SETMRK (B_USER + 2)
#define B_CLRMRK (B_USER + 3)
#define BUTTONS 6
#define BUTTONS_PERM 12
#define LABELS 4
/*** file scope type declarations ****************************************************************/
/*** forward declarations (file scope functions) *************************************************/
/*** file scope variables ************************************************************************/
static struct
{
mode_t mode;
const char *text;
gboolean selected;
WCheck *check;
} check_perm[BUTTONS_PERM] =
{
/* *INDENT-OFF* */
{ S_ISUID, N_("set &user ID on execution"), FALSE, NULL },
{ S_ISGID, N_("set &group ID on execution"), FALSE, NULL },
{ S_ISVTX, N_("stick&y bit"), FALSE, NULL },
{ S_IRUSR, N_("&read by owner"), FALSE, NULL },
{ S_IWUSR, N_("&write by owner"), FALSE, NULL },
{ S_IXUSR, N_("e&xecute/search by owner"), FALSE, NULL },
{ S_IRGRP, N_("rea&d by group"), FALSE, NULL },
{ S_IWGRP, N_("write by grou&p"), FALSE, NULL },
{ S_IXGRP, N_("execu&te/search by group"), FALSE, NULL },
{ S_IROTH, N_("read &by others"), FALSE, NULL },
{ S_IWOTH, N_("wr&ite by others"), FALSE, NULL },
{ S_IXOTH, N_("execute/searc&h by others"), FALSE, NULL }
/* *INDENT-ON* */
};
static int check_perm_len = 0;
static const char *file_info_labels[LABELS] = {
N_("Name:"),
N_("Permissions (octal):"),
N_("Owner name:"),
N_("Group name:")
};
static int file_info_labels_len = 0;
static struct
{
int ret_cmd;
button_flags_t flags;
int y; /* vertical position relatively to dialog bottom boundary */
int len;
const char *text;
} chmod_but[BUTTONS] =
{
/* *INDENT-OFF* */
{ B_SETALL, NORMAL_BUTTON, 6, 0, N_("Set &all") },
{ B_MARKED, NORMAL_BUTTON, 6, 0, N_("&Marked all") },
{ B_SETMRK, NORMAL_BUTTON, 5, 0, N_("S&et marked") },
{ B_CLRMRK, NORMAL_BUTTON, 5, 0, N_("C&lear marked") },
{ B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") },
{ B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") }
/* *INDENT-ON* */
};
static gboolean mode_change;
static int current_file;
static gboolean ignore_all;
static mode_t and_mask, or_mask, ch_mode;
static WLabel *statl;
static WGroupbox *file_gb;
/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */
static void
chmod_init (void)
{
static gboolean i18n = FALSE;
int i, len;
for (i = 0; i < BUTTONS_PERM; i++)
check_perm[i].selected = FALSE;
if (i18n)
return;
i18n = TRUE;
#ifdef ENABLE_NLS
for (i = 0; i < BUTTONS_PERM; i++)
check_perm[i].text = _(check_perm[i].text);
for (i = 0; i < LABELS; i++)
file_info_labels[i] = _(file_info_labels[i]);
for (i = 0; i < BUTTONS; i++)
chmod_but[i].text = _(chmod_but[i].text);
#endif /* ENABLE_NLS */
for (i = 0; i < BUTTONS_PERM; i++)
{
len = str_term_width1 (check_perm[i].text);
check_perm_len = MAX (check_perm_len, len);
}
check_perm_len += 1 + 3 + 1; /* mark, [x] and space */
for (i = 0; i < LABELS; i++)
{
len = str_term_width1 (file_info_labels[i]) + 2; /* spaces around */
file_info_labels_len = MAX (file_info_labels_len, len);
}
for (i = 0; i < BUTTONS; i++)
{
chmod_but[i].len = str_term_width1 (chmod_but[i].text) + 3; /* [], spaces and w/o & */
if (chmod_but[i].flags == DEFPUSH_BUTTON)
chmod_but[i].len += 2; /* <> */
}
}
/* --------------------------------------------------------------------------------------------- */
static void
chmod_draw_select (const WDialog * h, int Id)
{
widget_gotoyx (h, PY + Id + 1, PX + 1);
tty_print_char (check_perm[Id].selected ? '*' : ' ');
widget_gotoyx (h, PY + Id + 1, PX + 3);
}
/* --------------------------------------------------------------------------------------------- */
static void
chmod_toggle_select (const WDialog * h, int Id)
{
check_perm[Id].selected = !check_perm[Id].selected;
tty_setcolor (COLOR_NORMAL);
chmod_draw_select (h, Id);
}
/* --------------------------------------------------------------------------------------------- */
static void
chmod_refresh (const WDialog * h)
{
int i;
int y, x;
tty_setcolor (COLOR_NORMAL);
for (i = 0; i < BUTTONS_PERM; i++)
chmod_draw_select (h, i);
y = WIDGET (file_gb)->rect.y + 1;
x = WIDGET (file_gb)->rect.x + 2;
tty_gotoyx (y, x);
tty_print_string (file_info_labels[0]);
tty_gotoyx (y + 2, x);
tty_print_string (file_info_labels[1]);
tty_gotoyx (y + 4, x);
tty_print_string (file_info_labels[2]);
tty_gotoyx (y + 6, x);
tty_print_string (file_info_labels[3]);
}
/* --------------------------------------------------------------------------------------------- */
static cb_ret_t
chmod_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);
chmod_refresh (CONST_DIALOG (w->owner));
return MSG_HANDLED;
default:
return frame_callback (w, sender, msg, parm, data);
}
}
/* --------------------------------------------------------------------------------------------- */
static cb_ret_t
chmod_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
{
WGroup *g = GROUP (w);
WDialog *h = DIALOG (w);
switch (msg)
{
case MSG_NOTIFY:
{
/* handle checkboxes */
int i;
/* whether notification was sent by checkbox? */
for (i = 0; i < BUTTONS_PERM; i++)
if (sender == WIDGET (check_perm[i].check))
break;
if (i < BUTTONS_PERM)
{
ch_mode ^= check_perm[i].mode;
label_set_textv (statl, "%o", (unsigned int) ch_mode);
chmod_toggle_select (h, i);
mode_change = TRUE;
return MSG_HANDLED;
}
}
return MSG_NOT_HANDLED;
case MSG_KEY:
if (parm == 'T' || parm == 't' || parm == KEY_IC)
{
int i;
unsigned long id;
id = group_get_current_widget_id (g);
for (i = 0; i < BUTTONS_PERM; i++)
if (id == WIDGET (check_perm[i].check)->id)
break;
if (i < BUTTONS_PERM)
{
chmod_toggle_select (h, i);
if (parm == KEY_IC)
group_select_next_widget (g);
return MSG_HANDLED;
}
}
return MSG_NOT_HANDLED;
default:
return dlg_default_callback (w, sender, msg, parm, data);
}
}
/* --------------------------------------------------------------------------------------------- */
static WDialog *
chmod_dlg_create (WPanel * panel, const char *fname, const struct stat *sf_stat)
{
gboolean single_set;
WDialog *ch_dlg;
WGroup *g;
int lines, cols;
int i, y;
int perm_gb_len;
int file_gb_len;
const char *c_fname, *c_fown, *c_fgrp;
char buffer[BUF_TINY];
mode_change = FALSE;
single_set = (panel->marked < 2);
perm_gb_len = check_perm_len + 2;
file_gb_len = file_info_labels_len + 2;
cols = str_term_width1 (fname) + 2 + 1;
file_gb_len = MAX (file_gb_len, cols);
lines = single_set ? 20 : 23;
cols = perm_gb_len + file_gb_len + 1 + 6;
if (cols > COLS)
{
/* shrink the right groupbox */
cols = COLS;
file_gb_len = cols - (perm_gb_len + 1 + 6);
}
ch_dlg =
dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
chmod_callback, NULL, "[Chmod]", _("Chmod command"));
g = GROUP (ch_dlg);
/* draw background */
ch_dlg->bg->callback = chmod_bg_callback;
group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _("Permission")));
for (i = 0; i < BUTTONS_PERM; i++)
{
check_perm[i].check = check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0,
check_perm[i].text);
group_add_widget (g, check_perm[i].check);
}
file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _("File"));
group_add_widget (g, file_gb);
/* Set the labels */
y = PY + 2;
cols = PX + perm_gb_len + 3;
c_fname = str_trunc (fname, file_gb_len - 3);
group_add_widget (g, label_new (y, cols, c_fname));
g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
statl = label_new (y + 2, cols, buffer);
group_add_widget (g, statl);
c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3);
group_add_widget (g, label_new (y + 4, cols, c_fown));
c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3);
group_add_widget (g, label_new (y + 6, cols, c_fgrp));
if (!single_set)
{
i = 0;
group_add_widget (g, hline_new (lines - chmod_but[i].y - 1, -1, -1));
for (; i < BUTTONS - 2; i++)
{
y = lines - chmod_but[i].y;
group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
chmod_but[i].ret_cmd, chmod_but[i].flags,
chmod_but[i].text, NULL));
i++;
group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
chmod_but[i].ret_cmd, chmod_but[i].flags,
chmod_but[i].text, NULL));
}
}
i = BUTTONS - 2;
y = lines - chmod_but[i].y;
group_add_widget (g, hline_new (y - 1, -1, -1));
group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
NULL));
i++;
group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chmod_but[i].ret_cmd,
chmod_but[i].flags, chmod_but[i].text, NULL));
/* select first checkbox */
widget_select (WIDGET (check_perm[0].check));
return ch_dlg;
}
/* --------------------------------------------------------------------------------------------- */
static void
chmod_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_chmod (const vfs_path_t * p, mode_t m)
{
const char *fname = NULL;
while (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:
/* try next file */
return TRUE;
case 1:
ignore_all = TRUE;
/* try next file */
return TRUE;
case 2:
/* retry this file */
break;
case 3:
default:
/* stop remain files processing */
return FALSE;
}
}
return TRUE;
}
/* --------------------------------------------------------------------------------------------- */
static gboolean
do_chmod (WPanel * panel, const vfs_path_t * p, struct stat *sf)
{
gboolean ret;
sf->st_mode &= and_mask;
sf->st_mode |= or_mask;
ret = try_chmod (p, sf->st_mode);
do_file_mark (panel, current_file, 0);
return ret;
}
/* --------------------------------------------------------------------------------------------- */
static void
apply_mask (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
{
gboolean ok;
if (!do_chmod (panel, vpath, sf))
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_mode = sf->st_mode;
ok = do_chmod (panel, vpath, sf);
}
vfs_path_free (vpath, TRUE);
}
while (ok && panel->marked != 0);
}
/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */
void
chmod_cmd (WPanel * panel)
{
gboolean need_update;
gboolean end_chmod;
chmod_init ();
current_file = 0;
ignore_all = FALSE;
do
{ /* do while any files remaining */
vfs_path_t *vpath;
WDialog *ch_dlg;
struct stat sf_stat;
const GString *fname;
int i, result;
do_refresh ();
need_update = FALSE;
end_chmod = 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_mode = sf_stat.st_mode;
ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat);
result = dlg_run (ch_dlg);
switch (result)
{
case B_CANCEL:
end_chmod = TRUE;
break;
case B_ENTER:
if (mode_change)
{
if (panel->marked <= 1)
{
/* single or last file */
if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), fname->str,
unix_error_string (errno));
end_chmod = TRUE;
}
else if (!try_chmod (vpath, ch_mode))
{
/* stop multiple files processing */
result = B_CANCEL;
end_chmod = TRUE;
}
}
need_update = TRUE;
break;
case B_SETALL:
case B_MARKED:
and_mask = or_mask = 0;
and_mask = ~and_mask;
for (i = 0; i < BUTTONS_PERM; i++)
if (check_perm[i].selected || result == B_SETALL)
{
if (check_perm[i].check->state)
or_mask |= check_perm[i].mode;
else
and_mask &= ~check_perm[i].mode;
}
apply_mask (panel, vpath, &sf_stat);
need_update = TRUE;
end_chmod = TRUE;
break;
case B_SETMRK:
and_mask = or_mask = 0;
and_mask = ~and_mask;
for (i = 0; i < BUTTONS_PERM; i++)
if (check_perm[i].selected)
or_mask |= check_perm[i].mode;
apply_mask (panel, vpath, &sf_stat);
need_update = TRUE;
end_chmod = TRUE;
break;
case B_CLRMRK:
and_mask = or_mask = 0;
and_mask = ~and_mask;
for (i = 0; i < BUTTONS_PERM; i++)
if (check_perm[i].selected)
and_mask &= ~check_perm[i].mode;
apply_mask (panel, vpath, &sf_stat);
need_update = TRUE;
end_chmod = TRUE;
break;
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_chmod);
chmod_done (need_update);
}
/* --------------------------------------------------------------------------------------------- */