summaryrefslogtreecommitdiffstats
path: root/src/filemanager/chattr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager/chattr.c')
-rw-r--r--src/filemanager/chattr.c1358
1 files changed, 1358 insertions, 0 deletions
diff --git a/src/filemanager/chattr.c b/src/filemanager/chattr.c
new file mode 100644
index 0000000..08a5a99
--- /dev/null
+++ b/src/filemanager/chattr.c
@@ -0,0 +1,1358 @@
+/*
+ Chattr command -- for the Midnight Commander
+
+ Copyright (C) 2020-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2023
+
+ 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 chattr.c
+ * \brief Source: chattr command
+ */
+
+/* TODO: change attributes recursively (ticket #3109) */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ext2fs/ext2_fs.h>
+
+#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 "lib/util.h" /* x_basename() */
+
+#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 */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** 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);
+
+/* modifiable 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);
+ /* initially 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;
+
+ /* prepare 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 == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chattr (const vfs_path_t * p, unsigned long m)
+{
+ const char *fname = NULL;
+
+ while (mc_fsetflags (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 chattr \"%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_chattr (WPanel * panel, const vfs_path_t * p, unsigned long m)
+{
+ gboolean ret;
+
+ m &= and_mask;
+ m |= or_mask;
+
+ ret = try_chattr (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);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_fgetflags (vpath, &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
+ {
+ 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;
+ size_t i;
+ int result;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chattr = 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_fgetflags (vpath, &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 (mc_fsetflags (vpath, 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 (vpath, 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;
+}
+
+/* --------------------------------------------------------------------------------------------- */