/* Chattr command -- for the Midnight Commander Copyright (C) 2020-2022 Free Software Foundation, Inc. Written by: Andrew Borodin , 2020-2022 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 chattr.c * \brief Source: chattr command */ /* TODO: change attributes recursively (ticket #3109) */ #include #include #include #include #include #include #include "lib/global.h" #include "lib/tty/tty.h" /* tty_print*() */ #include "lib/tty/color.h" /* tty_setcolor() */ #include "lib/skin.h" /* COLOR_NORMAL, DISABLED_COLOR */ #include "lib/vfs/vfs.h" #include "lib/widget.h" #include "src/keymap.h" /* chattr_map */ #include "cmd.h" /* chattr_cmd(), chattr_get_as_str() */ /*** global variables ****************************************************************************/ /*** file scope macro definitions ****************************************************************/ #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 CHATTRBOXES(x) ((WChattrBoxes *)(x)) /*** file scope type declarations ****************************************************************/ typedef struct WFileAttrText WFileAttrText; struct WFileAttrText { Widget widget; /* base class */ char *filename; int filename_width; /* cached width of file name */ char attrs[32 + 1]; /* 32 bits in attributes (unsigned long) */ }; typedef struct WChattrBoxes WChattrBoxes; struct WChattrBoxes { WGroup base; /* base class */ int pos; /* The current checkbox selected */ int top; /* The first flag displayed */ }; /*** file scope variables ************************************************************************/ /* see /usr/include/ext2fs/ext2_fs.h * * EXT2_SECRM_FL 0x00000001 -- Secure deletion * EXT2_UNRM_FL 0x00000002 -- Undelete * EXT2_COMPR_FL 0x00000004 -- Compress file * EXT2_SYNC_FL 0x00000008 -- Synchronous updates * EXT2_IMMUTABLE_FL 0x00000010 -- Immutable file * EXT2_APPEND_FL 0x00000020 -- writes to file may only append * EXT2_NODUMP_FL 0x00000040 -- do not dump file * EXT2_NOATIME_FL 0x00000080 -- do not update atime * * Reserved for compression usage... * EXT2_DIRTY_FL 0x00000100 * EXT2_COMPRBLK_FL 0x00000200 -- One or more compressed clusters * EXT2_NOCOMPR_FL 0x00000400 -- Access raw compressed data * * nb: was previously EXT2_ECOMPR_FL * EXT4_ENCRYPT_FL 0x00000800 -- encrypted inode * * End compression flags --- maybe not all used * EXT2_BTREE_FL 0x00001000 -- btree format dir * EXT2_INDEX_FL 0x00001000 -- hash-indexed directory * EXT2_IMAGIC_FL 0x00002000 * EXT3_JOURNAL_DATA_FL 0x00004000 -- file data should be journaled * EXT2_NOTAIL_FL 0x00008000 -- file tail should not be merged * EXT2_DIRSYNC_FL 0x00010000 -- Synchronous directory modifications * EXT2_TOPDIR_FL 0x00020000 -- Top of directory hierarchies * EXT4_HUGE_FILE_FL 0x00040000 -- Set to each huge file * EXT4_EXTENTS_FL 0x00080000 -- Inode uses extents * EXT4_VERITY_FL 0x00100000 -- Verity protected inode * EXT4_EA_INODE_FL 0x00200000 -- Inode used for large EA * EXT4_EOFBLOCKS_FL 0x00400000 was here, unused * FS_NOCOW_FL 0x00800000 -- Do not cow file * EXT4_SNAPFILE_FL 0x01000000 -- Inode is a snapshot * FS_DAX_FL 0x02000000 -- Inode is DAX * EXT4_SNAPFILE_DELETED_FL 0x04000000 -- Snapshot is being deleted * EXT4_SNAPFILE_SHRUNK_FL 0x08000000 -- Snapshot shrink has completed * EXT4_INLINE_DATA_FL 0x10000000 -- Inode has inline data * EXT4_PROJINHERIT_FL 0x20000000 -- Create with parents projid * EXT4_CASEFOLD_FL 0x40000000 -- Casefolded file * 0x80000000 -- unused yet */ static struct { unsigned long flags; char attr; const char *text; gboolean selected; gboolean state; /* state of checkboxes */ } check_attr[] = { /* *INDENT-OFF* */ { EXT2_SECRM_FL, 's', N_("Secure deletion"), FALSE, FALSE }, { EXT2_UNRM_FL, 'u', N_("Undelete"), FALSE, FALSE }, { EXT2_SYNC_FL, 'S', N_("Synchronous updates"), FALSE, FALSE }, { EXT2_DIRSYNC_FL, 'D', N_("Synchronous directory updates"), FALSE, FALSE }, { EXT2_IMMUTABLE_FL, 'i', N_("Immutable"), FALSE, FALSE }, { EXT2_APPEND_FL, 'a', N_("Append only"), FALSE, FALSE }, { EXT2_NODUMP_FL, 'd', N_("No dump"), FALSE, FALSE }, { EXT2_NOATIME_FL, 'A', N_("No update atime"), FALSE, FALSE }, { EXT2_COMPR_FL, 'c', N_("Compress"), FALSE, FALSE }, #ifdef EXT2_COMPRBLK_FL /* removed in v1.43-WIP-2015-05-18 ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */ { EXT2_COMPRBLK_FL, 'B', N_("Compressed clusters"), FALSE, FALSE }, #endif #ifdef EXT2_DIRTY_FL /* removed in v1.43-WIP-2015-05-18 ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */ { EXT2_DIRTY_FL, 'Z', N_("Compressed dirty file"), FALSE, FALSE }, #endif #ifdef EXT2_NOCOMPR_FL /* removed in v1.43-WIP-2015-05-18 ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */ { EXT2_NOCOMPR_FL, 'X', N_("Compression raw access"), FALSE, FALSE }, #endif #ifdef EXT4_ENCRYPT_FL { EXT4_ENCRYPT_FL, 'E', N_("Encrypted inode"), FALSE, FALSE }, #endif { EXT3_JOURNAL_DATA_FL, 'j', N_("Journaled data"), FALSE, FALSE }, { EXT2_INDEX_FL, 'I', N_("Indexed directory"), FALSE, FALSE }, { EXT2_NOTAIL_FL, 't', N_("No tail merging"), FALSE, FALSE }, { EXT2_TOPDIR_FL, 'T', N_("Top of directory hierarchies"), FALSE, FALSE }, { EXT4_EXTENTS_FL, 'e', N_("Inode uses extents"), FALSE, FALSE }, #ifdef EXT4_HUGE_FILE_FL /* removed in v1.43.9 ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */ { EXT4_HUGE_FILE_FL, 'h', N_("Huge_file"), FALSE, FALSE }, #endif { FS_NOCOW_FL, 'C', N_("No COW"), FALSE, FALSE }, #ifdef FS_DAX_FL /* added in v1.45.7 ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */ { FS_DAX_FL, 'x', N_("Direct access for files"), FALSE, FALSE }, #endif #ifdef EXT4_CASEFOLD_FL /* added in v1.45.0 ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */ { EXT4_CASEFOLD_FL, 'F', N_("Casefolded file"), FALSE, FALSE }, #endif #ifdef EXT4_INLINE_DATA_FL { EXT4_INLINE_DATA_FL, 'N', N_("Inode has inline data"), FALSE, FALSE }, #endif #ifdef EXT4_PROJINHERIT_FL /* added in v1.43-WIP-2016-05-12 ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07 97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */ { EXT4_PROJINHERIT_FL, 'P', N_("Project hierarchy"), FALSE, FALSE }, #endif #ifdef EXT4_VERITY_FL /* added in v1.44.4 ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14 v1.44.5 ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */ { EXT4_VERITY_FL, 'V', N_("Verity protected inode"), FALSE, FALSE } #endif /* *INDENT-ON* */ }; /* number of attributes */ static const size_t check_attr_num = G_N_ELEMENTS (check_attr); /* modifable attribute numbers */ static int check_attr_mod[32]; static int check_attr_mod_num = 0; /* 0..31 */ /* maximum width of attribute text */ static int check_attr_width = 0; static struct { int ret_cmd; button_flags_t flags; int width; const char *text; Widget *button; } chattr_but[BUTTONS] = { /* *INDENT-OFF* */ /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_("Set &all"), NULL }, /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_("&Marked all"), NULL }, /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_("S&et marked"), NULL }, /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_("C&lear marked"), NULL }, /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_("&Set"), NULL }, /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_("&Cancel"), NULL } /* *INDENT-ON* */ }; static gboolean flags_changed; static int current_file; static gboolean ignore_all; static unsigned long and_mask, or_mask, flags; static WFileAttrText *file_attr; /* x-coord of widget in the dialog */ static const int wx = 3; /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ static inline gboolean chattr_is_modifiable (size_t i) { return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0); } /* --------------------------------------------------------------------------------------------- */ static void chattr_fill_str (unsigned long attr, char *str) { size_t i; for (i = 0; i < check_attr_num; i++) str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-'; str[check_attr_num] = '\0'; } /* --------------------------------------------------------------------------------------------- */ static void fileattrtext_fill (WFileAttrText * fat, unsigned long attr) { chattr_fill_str (attr, fat->attrs); widget_draw (WIDGET (fat)); } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t fileattrtext_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) { WFileAttrText *fat = (WFileAttrText *) w; switch (msg) { case MSG_DRAW: { int color; size_t i; color = COLOR_NORMAL; tty_setcolor (color); if (w->rect.cols > fat->filename_width) { widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2); tty_print_string (fat->filename); } else { widget_gotoyx (w, 0, 0); tty_print_string (str_trunc (fat->filename, w->rect.cols)); } /* hope that w->cols is greater than check_attr_num */ widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2); for (i = 0; i < check_attr_num; i++) { /* Do not set new color for each symbol. Try to use previous color. */ if (chattr_is_modifiable (i)) { if (color == DISABLED_COLOR) { color = COLOR_NORMAL; tty_setcolor (color); } } else { if (color != DISABLED_COLOR) { color = DISABLED_COLOR; tty_setcolor (color); } } tty_print_char (fat->attrs[i]); } return MSG_HANDLED; } case MSG_RESIZE: { const WRect *wo = &CONST_WIDGET (w->owner)->rect; widget_default_callback (w, sender, msg, parm, data); /* intially file name may be wider than screen */ if (fat->filename_width > wo->cols - wx * 2) { w->rect.x = wo->x + wx; w->rect.cols = wo->cols - wx * 2; } return MSG_HANDLED; } case MSG_DESTROY: g_free (fat->filename); return MSG_HANDLED; default: return widget_default_callback (w, sender, msg, parm, data); } } /* --------------------------------------------------------------------------------------------- */ static WFileAttrText * fileattrtext_new (int y, int x, const char *filename, unsigned long attr) { WRect r = { y, x, 2, 1 }; WFileAttrText *fat; int width; width = str_term_width1 (filename); r.cols = MAX (width, (int) check_attr_num); fat = g_new (WFileAttrText, 1); widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL); fat->filename = g_strdup (filename); fat->filename_width = width; fileattrtext_fill (fat, attr); return fat; } /* --------------------------------------------------------------------------------------------- */ static void chattr_draw_select (const Widget * w, gboolean selected) { widget_gotoyx (w, 0, -1); tty_print_char (selected ? '*' : ' '); widget_gotoyx (w, 0, 1); } /* --------------------------------------------------------------------------------------------- */ static void chattr_toggle_select (const WChattrBoxes * cb, int Id) { Widget *w; /* find checkbox */ w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top)); check_attr[Id].selected = !check_attr[Id].selected; tty_setcolor (COLOR_NORMAL); chattr_draw_select (w, check_attr[Id].selected); } /* --------------------------------------------------------------------------------------------- */ static inline void chattrboxes_draw_scrollbar (const WChattrBoxes * cb) { const Widget *w = CONST_WIDGET (cb); int max_line; int line; int i; /* Are we at the top? */ widget_gotoyx (w, 0, w->rect.cols); if (cb->top == 0) tty_print_one_vline (TRUE); else tty_print_char ('^'); max_line = w->rect.lines - 1; /* Are we at the bottom? */ widget_gotoyx (w, max_line, w->rect.cols); if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num) tty_print_one_vline (TRUE); else tty_print_char ('v'); /* Now draw the nice relative pointer */ line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num; for (i = 1; i < max_line; i++) { widget_gotoyx (w, i, w->rect.cols); if (i != line) tty_print_one_vline (TRUE); else tty_print_char ('*'); } } /* --------------------------------------------------------------------------------------------- */ static void chattrboxes_draw (WChattrBoxes * cb) { Widget *w = WIDGET (cb); int i; GList *l; const int *colors; colors = widget_get_colors (w); tty_setcolor (colors[DLG_COLOR_NORMAL]); tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' '); /* redraw checkboxes */ group_default_callback (w, NULL, MSG_DRAW, 0, NULL); /* draw scrollbar */ tty_setcolor (colors[DLG_COLOR_NORMAL]); if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines) chattrboxes_draw_scrollbar (cb); /* mark selected checkboxes */ for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l)) chattr_draw_select (WIDGET (l->data), check_attr[i].selected); } /* --------------------------------------------------------------------------------------------- */ static void chattrboxes_rename (WChattrBoxes * cb) { Widget *w = WIDGET (cb); gboolean active; int i; GList *l; char btext[BUF_SMALL]; /* FIXME: is 128 bytes enough? */ active = widget_get_state (w, WST_ACTIVE); /* lock the group to avoid redraw of checkboxes individually */ if (active) widget_set_state (w, WST_SUSPENDED, TRUE); for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l)) { WCheck *c = CHECK (l->data); int m; m = check_attr_mod[i]; g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text); check_set_text (c, btext); c->state = check_attr[m].state; } /* unlock */ if (active) widget_set_state (w, WST_ACTIVE, TRUE); widget_draw (w); } /* --------------------------------------------------------------------------------------------- */ static void checkboxes_save_state (const WChattrBoxes * cb) { int i; GList *l; for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l)) { int m; m = check_attr_mod[i]; check_attr[m].state = CHECK (l->data)->state; } } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_down (WChattrBoxes * cb) { if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1) { /* We are on the last checkbox. Keep this position. */ if (cb->pos == check_attr_mod_num - 1) /* get out of widget */ return MSG_NOT_HANDLED; /* emulate scroll of checkboxes */ checkboxes_save_state (cb); cb->pos++; cb->top++; chattrboxes_rename (cb); } else /* cb->pos > cb-top */ { GList *l; /* select next checkbox */ cb->pos++; l = g_list_next (GROUP (cb)->current); widget_select (WIDGET (l->data)); } return MSG_HANDLED; } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_page_down (WChattrBoxes * cb) { WGroup *g = GROUP (cb); GList *l; if (cb->pos == check_attr_mod_num - 1) { /* We are on the last checkbox. Keep this position. Do nothing. */ l = g_list_last (g->widgets); } else { int i = WIDGET (cb)->rect.lines; checkboxes_save_state (cb); if (cb->top > check_attr_mod_num - 2 * i) i = check_attr_mod_num - i - cb->top; if (cb->top + i < 0) i = -cb->top; if (i == 0) { cb->pos = check_attr_mod_num - 1; cb->top += i; l = g_list_last (g->widgets); } else { cb->pos += i; cb->top += i; l = g_list_nth (g->widgets, cb->pos - cb->top); } chattrboxes_rename (cb); } widget_select (WIDGET (l->data)); return MSG_HANDLED; } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_end (WChattrBoxes * cb) { GList *l; checkboxes_save_state (cb); cb->pos = check_attr_mod_num - 1; cb->top = cb->pos - WIDGET (cb)->rect.lines + 1; l = g_list_last (GROUP (cb)->widgets); chattrboxes_rename (cb); widget_select (WIDGET (l->data)); return MSG_HANDLED; } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_up (WChattrBoxes * cb) { if (cb->pos == cb->top) { /* We are on the first checkbox. Keep this position. */ if (cb->top == 0) /* get out of widget */ return MSG_NOT_HANDLED; /* emulate scroll of checkboxes */ checkboxes_save_state (cb); cb->pos--; cb->top--; chattrboxes_rename (cb); } else /* cb->pos > cb-top */ { GList *l; /* select previous checkbox */ cb->pos--; l = g_list_previous (GROUP (cb)->current); widget_select (WIDGET (l->data)); } return MSG_HANDLED; } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_page_up (WChattrBoxes * cb) { WGroup *g = GROUP (cb); GList *l; if (cb->pos == 0 && cb->top == 0) { /* We are on the first checkbox. Keep this position. Do nothing. */ l = g_list_first (g->widgets); } else { int i = WIDGET (cb)->rect.lines; checkboxes_save_state (cb); if (cb->top < i) i = cb->top; if (i == 0) { cb->pos = 0; cb->top -= i; l = g_list_first (g->widgets); } else { cb->pos -= i; cb->top -= i; l = g_list_nth (g->widgets, cb->pos - cb->top); } chattrboxes_rename (cb); } widget_select (WIDGET (l->data)); return MSG_HANDLED; } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_home (WChattrBoxes * cb) { GList *l; checkboxes_save_state (cb); cb->pos = 0; cb->top = 0; l = g_list_first (GROUP (cb)->widgets); chattrboxes_rename (cb); widget_select (WIDGET (l->data)); return MSG_HANDLED; } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_execute_cmd (WChattrBoxes * cb, long command) { switch (command) { case CK_Down: return chattrboxes_down (cb); case CK_PageDown: return chattrboxes_page_down (cb); case CK_Bottom: return chattrboxes_end (cb); case CK_Up: return chattrboxes_up (cb); case CK_PageUp: return chattrboxes_page_up (cb); case CK_Top: return chattrboxes_home (cb); case CK_Mark: case CK_MarkAndDown: { chattr_toggle_select (cb, cb->pos); /* FIXME */ if (command == CK_MarkAndDown) chattrboxes_down (cb); return MSG_HANDLED; } default: return MSG_NOT_HANDLED; } } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_key (WChattrBoxes * cb, int key) { long command; command = widget_lookup_key (WIDGET (cb), key); if (command == CK_IgnoreKey) return MSG_NOT_HANDLED; return chattrboxes_execute_cmd (cb, command); } /* --------------------------------------------------------------------------------------------- */ static cb_ret_t chattrboxes_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) { WChattrBoxes *cb = CHATTRBOXES (w); WGroup *g = GROUP (w); switch (msg) { case MSG_DRAW: chattrboxes_draw (cb); return MSG_HANDLED; case MSG_NOTIFY: { /* handle checkboxes */ int i; i = g_list_index (g->widgets, sender); if (i >= 0) { int m; i += cb->top; m = check_attr_mod[i]; flags ^= check_attr[m].flags; fileattrtext_fill (file_attr, flags); chattr_toggle_select (cb, i); flags_changed = TRUE; return MSG_HANDLED; } } return MSG_NOT_HANDLED; case MSG_CHANGED_FOCUS: /* sender is one of chattr checkboxes */ if (widget_get_state (sender, WST_FOCUSED)) { int i; i = g_list_index (g->widgets, sender); cb->pos = cb->top + i; } return MSG_HANDLED; case MSG_KEY: { cb_ret_t ret; ret = chattrboxes_key (cb, parm); if (ret != MSG_HANDLED) ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL); return ret; } case MSG_ACTION: return chattrboxes_execute_cmd (cb, parm); case MSG_DESTROY: /* save all states */ checkboxes_save_state (cb); MC_FALLTHROUGH; default: return group_default_callback (w, sender, msg, parm, data); } } /* --------------------------------------------------------------------------------------------- */ static int chattrboxes_handle_mouse_event (Widget * w, Gpm_Event * event) { int mou; mou = mouse_handle_event (w, event); if (mou == MOU_UNHANDLED) mou = group_handle_mouse_event (w, event); return mou; } /* --------------------------------------------------------------------------------------------- */ static void chattrboxes_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) { WChattrBoxes *cb = CHATTRBOXES (w); (void) event; switch (msg) { case MSG_MOUSE_SCROLL_UP: chattrboxes_up (cb); break; case MSG_MOUSE_SCROLL_DOWN: chattrboxes_down (cb); break; default: /* return MOU_UNHANDLED */ event->result.abort = TRUE; break; } } /* --------------------------------------------------------------------------------------------- */ static WChattrBoxes * chattrboxes_new (const WRect * r) { WChattrBoxes *cb; Widget *w; WGroup *cbg; int i; cb = g_new0 (WChattrBoxes, 1); w = WIDGET (cb); cbg = GROUP (cb); group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback); w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR; w->mouse_handler = chattrboxes_handle_mouse_event; w->keymap = chattr_map; /* create checkboxes */ for (i = 0; i < r->lines; i++) { int m; WCheck *check; m = check_attr_mod[i]; check = check_new (i, 0, check_attr[m].state, NULL); group_add_widget (cbg, check); } chattrboxes_rename (cb); /* select first checkbox */ cbg->current = cbg->widgets; return cb; } /* --------------------------------------------------------------------------------------------- */ static void chattr_init (void) { static gboolean i18n = FALSE; size_t i; for (i = 0; i < check_attr_num; i++) check_attr[i].selected = FALSE; if (i18n) return; i18n = TRUE; for (i = 0; i < check_attr_num; i++) if (chattr_is_modifiable (i)) { int width; #ifdef ENABLE_NLS check_attr[i].text = _(check_attr[i].text); #endif check_attr_mod[check_attr_mod_num++] = i; width = 4 + str_term_width1 (check_attr[i].text); /* "(Q) text " */ check_attr_width = MAX (check_attr_width, width); } check_attr_width += 1 + 3 + 1; /* mark, [x] and space */ for (i = 0; i < BUTTONS; i++) { #ifdef ENABLE_NLS chattr_but[i].text = _(chattr_but[i].text); #endif chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3; /* [], spaces and w/o & */ if (chattr_but[i].flags == DEFPUSH_BUTTON) chattr_but[i].width += 2; /* <> */ } } /* --------------------------------------------------------------------------------------------- */ static WDialog * chattr_dlg_create (WPanel * panel, const char *fname, unsigned long attr) { Widget *mw = WIDGET (WIDGET (panel)->owner); gboolean single_set; WDialog *ch_dlg; int lines, cols; int checkboxes_lines = check_attr_mod_num; size_t i; int y; Widget *dw; WGroup *dg; WChattrBoxes *cb; const int cb_scrollbar_width = 1; WRect r; /* prepate to set up checkbox states */ for (i = 0; i < check_attr_num; i++) check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0; cols = check_attr_width + cb_scrollbar_width; single_set = (panel->marked < 2); lines = 5 + checkboxes_lines + 4; if (!single_set) lines += 3; if (lines >= mw->rect.lines - 2) { int dl; dl = lines - (mw->rect.lines - 2); lines -= dl; checkboxes_lines -= dl; } ch_dlg = dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors, dlg_default_callback, NULL, "[Chattr]", _("Chattr command")); dg = GROUP (ch_dlg); dw = WIDGET (ch_dlg); y = 2; file_attr = fileattrtext_new (y, wx, fname, attr); group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL); y += WIDGET (file_attr)->rect.lines; group_add_widget (dg, hline_new (y++, -1, -1)); if (cols < WIDGET (file_attr)->rect.cols) { r = dw->rect; cols = WIDGET (file_attr)->rect.cols; cols = MIN (cols, mw->rect.cols - wx * 2); r.cols = cols + wx * 2; r.lines = lines; widget_set_size_rect (dw, &r); } checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines); rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols); cb = chattrboxes_new (&r); group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL); y += checkboxes_lines - 1; cols = 0; for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++) { if (i == 0 || i == BUTTONS - 2) group_add_widget (dg, hline_new (y++, -1, -1)); chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width, chattr_but[i].ret_cmd, chattr_but[i].flags, chattr_but[i].text, NULL)); group_add_widget (dg, chattr_but[i].button); i++; chattr_but[i].button = WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd, chattr_but[i].flags, chattr_but[i].text, NULL)); group_add_widget (dg, chattr_but[i].button); /* two buttons in a row */ cols = MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols); } /* adjust dialog size and button positions */ cols += 6; if (cols > dw->rect.cols) { r = dw->rect; r.lines = lines; r.cols = cols; widget_set_size_rect (dw, &r); /* dialog center */ cols = dw->rect.x + dw->rect.cols / 2 + 1; for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++) { Widget *b; b = chattr_but[i++].button; r = b->rect; r.x = cols - r.cols; widget_set_size_rect (b, &r); b = chattr_but[i].button; r = b->rect; r.x = cols + 1; widget_set_size_rect (b, &r); } } widget_select (WIDGET (cb)); return ch_dlg; } /* --------------------------------------------------------------------------------------------- */ static void chattr_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) current_file++; return panel->dir.list[current_file].fname; } /* --------------------------------------------------------------------------------------------- */ static gboolean try_chattr (const char *p, unsigned long m) { while (fsetflags (p, m) == -1 && !ignore_all) { int my_errno = errno; int result; char *msg; msg = g_strdup_printf (_("Cannot chattr \"%s\"\n%s"), x_basename (p), 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_chattr (WPanel * panel, const vfs_path_t * p, unsigned long m) { gboolean ret; m &= and_mask; m |= or_mask; ret = try_chattr (vfs_path_as_str (p), m); do_file_mark (panel, current_file, 0); return ret; } /* --------------------------------------------------------------------------------------------- */ static void chattr_apply_mask (WPanel * panel, vfs_path_t * vpath, unsigned long m) { gboolean ok; if (!do_chattr (panel, vpath, m)) return; do { const GString *fname; fname = next_file (panel); ok = (fgetflags (fname->str, &m) == 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 { vpath = vfs_path_from_str (fname->str); flags = m; ok = do_chattr (panel, vpath, m); vfs_path_free (vpath, TRUE); } } while (ok && panel->marked != 0); } /* --------------------------------------------------------------------------------------------- */ /*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ void chattr_cmd (WPanel * panel) { gboolean need_update = FALSE; gboolean end_chattr = FALSE; chattr_init (); current_file = 0; ignore_all = FALSE; do { /* do while any files remaining */ vfs_path_t *vpath; WDialog *ch_dlg; const GString *fname; const char *fname2; size_t i; int result; if (!vfs_current_is_local ()) { message (D_ERROR, MSG_ERROR, "%s", _("Cannot change attributes on non-local filesystems")); break; } do_refresh (); need_update = FALSE; end_chattr = FALSE; if (panel->marked != 0) fname = next_file (panel); /* next marked file */ else fname = selection (panel)->fname; /* single file */ vpath = vfs_path_from_str (fname->str); fname2 = vfs_path_as_str (vpath); if (fgetflags (fname2, &flags) != 0) { message (D_ERROR, MSG_ERROR, _("Cannot get flags of \"%s\"\n%s"), fname->str, unix_error_string (errno)); vfs_path_free (vpath, TRUE); break; } flags_changed = FALSE; ch_dlg = chattr_dlg_create (panel, fname->str, flags); result = dlg_run (ch_dlg); widget_destroy (WIDGET (ch_dlg)); switch (result) { case B_CANCEL: end_chattr = TRUE; break; case B_ENTER: if (flags_changed) { if (panel->marked <= 1) { /* single or last file */ if (fsetflags (fname2, flags) == -1 && !ignore_all) message (D_ERROR, MSG_ERROR, _("Cannot chattr \"%s\"\n%s"), fname->str, unix_error_string (errno)); end_chattr = TRUE; } else if (!try_chattr (fname2, flags)) { /* stop multiple files processing */ result = B_CANCEL; end_chattr = TRUE; } } need_update = TRUE; break; case B_SETALL: case B_MARKED: or_mask = 0; and_mask = ~0; for (i = 0; i < check_attr_num; i++) if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL)) { if (check_attr[i].state) or_mask |= check_attr[i].flags; else and_mask &= ~check_attr[i].flags; } chattr_apply_mask (panel, vpath, flags); need_update = TRUE; end_chattr = TRUE; break; case B_SETMRK: or_mask = 0; and_mask = ~0; for (i = 0; i < check_attr_num; i++) if (chattr_is_modifiable (i) && check_attr[i].selected) or_mask |= check_attr[i].flags; chattr_apply_mask (panel, vpath, flags); need_update = TRUE; end_chattr = TRUE; break; case B_CLRMRK: or_mask = 0; and_mask = ~0; for (i = 0; i < check_attr_num; i++) if (chattr_is_modifiable (i) && check_attr[i].selected) and_mask &= ~check_attr[i].flags; chattr_apply_mask (panel, vpath, flags); need_update = TRUE; end_chattr = 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); } while (panel->marked != 0 && !end_chattr); chattr_done (need_update); } /* --------------------------------------------------------------------------------------------- */ const char * chattr_get_as_str (unsigned long attr) { static char str[32 + 1]; /* 32 bits in attributes (unsigned long) */ chattr_fill_str (attr, str); return str; } /* --------------------------------------------------------------------------------------------- */