/* 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 . */ /** \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); } /* --------------------------------------------------------------------------------------------- */