summaryrefslogtreecommitdiffstats
path: root/src/filemanager/chmod.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager/chmod.c')
-rw-r--r--src/filemanager/chmod.c664
1 files changed, 664 insertions, 0 deletions
diff --git a/src/filemanager/chmod.c b/src/filemanager/chmod.c
new file mode 100644
index 0000000..c93bcbc
--- /dev/null
+++ b/src/filemanager/chmod.c
@@ -0,0 +1,664 @@
+/*
+ Chmod command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ 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 chmod.c
+ * \brief Source: chmod command
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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);
+}
+
+/* --------------------------------------------------------------------------------------------- */