root/src/filemanager/chattr.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. chattr_is_modifiable
  2. chattr_fill_str
  3. fileattrtext_fill
  4. fileattrtext_callback
  5. fileattrtext_new
  6. chattr_draw_select
  7. chattr_toggle_select
  8. chattrboxes_draw_scrollbar
  9. chattrboxes_draw
  10. chattrboxes_rename
  11. checkboxes_save_state
  12. chattrboxes_down
  13. chattrboxes_page_down
  14. chattrboxes_end
  15. chattrboxes_up
  16. chattrboxes_page_up
  17. chattrboxes_home
  18. chattrboxes_execute_cmd
  19. chattrboxes_key
  20. chattrboxes_callback
  21. chattrboxes_handle_mouse_event
  22. chattrboxes_mouse_callback
  23. chattrboxes_new
  24. chattr_init
  25. chattr_dlg_create
  26. chattr_done
  27. try_chattr
  28. do_chattr
  29. chattr_apply_mask
  30. chattr_cmd
  31. chattr_get_as_str

   1 /*
   2    Chattr command -- for the Midnight Commander
   3 
   4    Copyright (C) 2020-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Andrew Borodin <aborodin@vmail.ru>, 2020-2023
   9 
  10    This file is part of the Midnight Commander.
  11 
  12    The Midnight Commander is free software: you can redistribute it
  13    and/or modify it under the terms of the GNU General Public License as
  14    published by the Free Software Foundation, either version 3 of the License,
  15    or (at your option) any later version.
  16 
  17    The Midnight Commander is distributed in the hope that it will be useful,
  18    but WITHOUT ANY WARRANTY; without even the implied warranty of
  19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20    GNU General Public License for more details.
  21 
  22    You should have received a copy of the GNU General Public License
  23    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  24  */
  25 
  26 /** \file chattr.c
  27  *  \brief Source: chattr command
  28  */
  29 
  30 /* TODO: change attributes recursively (ticket #3109) */
  31 
  32 #include <config.h>
  33 
  34 #include <errno.h>
  35 #include <sys/types.h>
  36 #include <sys/stat.h>
  37 
  38 #include <ext2fs/ext2_fs.h>
  39 
  40 #include "lib/global.h"
  41 
  42 #include "lib/tty/tty.h"    // tty_print*()
  43 #include "lib/tty/color.h"  // tty_setcolor()
  44 #include "lib/skin.h"       // COLOR_NORMAL, DISABLED_COLOR
  45 #include "lib/vfs/vfs.h"
  46 #include "lib/widget.h"
  47 #include "lib/util.h"  // x_basename()
  48 
  49 #include "src/keymap.h"  // chattr_map
  50 
  51 #include "cmd.h"  // chattr_cmd(), chattr_get_as_str()
  52 
  53 /*** global variables ****************************************************************************/
  54 
  55 /*** file scope macro definitions ****************************************************************/
  56 
  57 #define B_MARKED       B_USER
  58 #define B_SETALL       (B_USER + 1)
  59 #define B_SETMRK       (B_USER + 2)
  60 #define B_CLRMRK       (B_USER + 3)
  61 
  62 #define BUTTONS        6
  63 
  64 #define CHATTRBOXES(x) ((WChattrBoxes *) (x))
  65 
  66 /*** file scope type declarations ****************************************************************/
  67 
  68 typedef struct WFileAttrText WFileAttrText;
  69 
  70 struct WFileAttrText
  71 {
  72     Widget widget;  // base class
  73 
  74     char *filename;
  75     int filename_width;  // cached width of file name
  76     char attrs[32 + 1];  // 32 bits in attributes (unsigned long)
  77 };
  78 
  79 typedef struct WChattrBoxes WChattrBoxes;
  80 
  81 struct WChattrBoxes
  82 {
  83     WGroup base;  // base class
  84 
  85     int pos;  // The current checkbox selected
  86     int top;  // The first flag displayed
  87 };
  88 
  89 /*** forward declarations (file scope functions) *************************************************/
  90 
  91 /*** file scope variables ************************************************************************/
  92 
  93 /* see /usr/include/ext2fs/ext2_fs.h
  94  *
  95  * EXT2_SECRM_FL            0x00000001 -- Secure deletion
  96  * EXT2_UNRM_FL             0x00000002 -- Undelete
  97  * EXT2_COMPR_FL            0x00000004 -- Compress file
  98  * EXT2_SYNC_FL             0x00000008 -- Synchronous updates
  99  * EXT2_IMMUTABLE_FL        0x00000010 -- Immutable file
 100  * EXT2_APPEND_FL           0x00000020 -- writes to file may only append
 101  * EXT2_NODUMP_FL           0x00000040 -- do not dump file
 102  * EXT2_NOATIME_FL          0x00000080 -- do not update atime
 103  * * Reserved for compression usage...
 104  * EXT2_DIRTY_FL            0x00000100
 105  * EXT2_COMPRBLK_FL         0x00000200 -- One or more compressed clusters
 106  * EXT2_NOCOMPR_FL          0x00000400 -- Access raw compressed data
 107  * * nb: was previously EXT2_ECOMPR_FL
 108  * EXT4_ENCRYPT_FL          0x00000800 -- encrypted inode
 109  * * End compression flags --- maybe not all used
 110  * EXT2_BTREE_FL            0x00001000 -- btree format dir
 111  * EXT2_INDEX_FL            0x00001000 -- hash-indexed directory
 112  * EXT2_IMAGIC_FL           0x00002000
 113  * EXT3_JOURNAL_DATA_FL     0x00004000 -- file data should be journaled
 114  * EXT2_NOTAIL_FL           0x00008000 -- file tail should not be merged
 115  * EXT2_DIRSYNC_FL          0x00010000 -- Synchronous directory modifications
 116  * EXT2_TOPDIR_FL           0x00020000 -- Top of directory hierarchies
 117  * EXT4_HUGE_FILE_FL        0x00040000 -- Set to each huge file
 118  * EXT4_EXTENTS_FL          0x00080000 -- Inode uses extents
 119  * EXT4_VERITY_FL           0x00100000 -- Verity protected inode
 120  * EXT4_EA_INODE_FL         0x00200000 -- Inode used for large EA
 121  * EXT4_EOFBLOCKS_FL        0x00400000 was here, unused
 122  * FS_NOCOW_FL              0x00800000 -- Do not cow file
 123  * EXT4_SNAPFILE_FL         0x01000000 -- Inode is a snapshot
 124  * FS_DAX_FL                0x02000000 -- Inode is DAX
 125  * EXT4_SNAPFILE_DELETED_FL 0x04000000 -- Snapshot is being deleted
 126  * EXT4_SNAPFILE_SHRUNK_FL  0x08000000 -- Snapshot shrink has completed
 127  * EXT4_INLINE_DATA_FL      0x10000000 -- Inode has inline data
 128  * EXT4_PROJINHERIT_FL      0x20000000 -- Create with parents projid
 129  * EXT4_CASEFOLD_FL         0x40000000 -- Casefolded file
 130  *                          0x80000000 -- unused yet
 131  */
 132 
 133 static struct
 134 {
 135     unsigned long flags;
 136     char attr;
 137     const char *text;
 138     gboolean selected;
 139     gboolean state;  // state of checkboxes
 140 } check_attr[] = {
 141     { EXT2_SECRM_FL, 's', N_ ("Secure deletion"), FALSE, FALSE },
 142     { EXT2_UNRM_FL, 'u', N_ ("Undelete"), FALSE, FALSE },
 143     { EXT2_SYNC_FL, 'S', N_ ("Synchronous updates"), FALSE, FALSE },
 144     { EXT2_DIRSYNC_FL, 'D', N_ ("Synchronous directory updates"), FALSE, FALSE },
 145     { EXT2_IMMUTABLE_FL, 'i', N_ ("Immutable"), FALSE, FALSE },
 146     { EXT2_APPEND_FL, 'a', N_ ("Append only"), FALSE, FALSE },
 147     { EXT2_NODUMP_FL, 'd', N_ ("No dump"), FALSE, FALSE },
 148     { EXT2_NOATIME_FL, 'A', N_ ("No update atime"), FALSE, FALSE },
 149     { EXT2_COMPR_FL, 'c', N_ ("Compress"), FALSE, FALSE },
 150 #ifdef EXT2_COMPRBLK_FL
 151     /* removed in v1.43-WIP-2015-05-18
 152        ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
 153     { EXT2_COMPRBLK_FL, 'B', N_ ("Compressed clusters"), FALSE, FALSE },
 154 #endif
 155 #ifdef EXT2_DIRTY_FL
 156     /* removed in v1.43-WIP-2015-05-18
 157        ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
 158     { EXT2_DIRTY_FL, 'Z', N_ ("Compressed dirty file"), FALSE, FALSE },
 159 #endif
 160 #ifdef EXT2_NOCOMPR_FL
 161     /* removed in v1.43-WIP-2015-05-18
 162        ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
 163     { EXT2_NOCOMPR_FL, 'X', N_ ("Compression raw access"), FALSE, FALSE },
 164 #endif
 165 #ifdef EXT4_ENCRYPT_FL
 166     { EXT4_ENCRYPT_FL, 'E', N_ ("Encrypted inode"), FALSE, FALSE },
 167 #endif
 168     { EXT3_JOURNAL_DATA_FL, 'j', N_ ("Journaled data"), FALSE, FALSE },
 169     { EXT2_INDEX_FL, 'I', N_ ("Indexed directory"), FALSE, FALSE },
 170     { EXT2_NOTAIL_FL, 't', N_ ("No tail merging"), FALSE, FALSE },
 171     { EXT2_TOPDIR_FL, 'T', N_ ("Top of directory hierarchies"), FALSE, FALSE },
 172     { EXT4_EXTENTS_FL, 'e', N_ ("Inode uses extents"), FALSE, FALSE },
 173 #ifdef EXT4_HUGE_FILE_FL
 174     /* removed in v1.43.9
 175        ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */
 176     { EXT4_HUGE_FILE_FL, 'h', N_ ("Huge_file"), FALSE, FALSE },
 177 #endif
 178     { FS_NOCOW_FL, 'C', N_ ("No COW"), FALSE, FALSE },
 179 #ifdef FS_DAX_FL
 180     /* added in v1.45.7
 181        ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */
 182     { FS_DAX_FL, 'x', N_ ("Direct access for files"), FALSE, FALSE },
 183 #endif
 184 #ifdef EXT4_CASEFOLD_FL
 185     /* added in v1.45.0
 186        ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */
 187     { EXT4_CASEFOLD_FL, 'F', N_ ("Casefolded file"), FALSE, FALSE },
 188 #endif
 189 #ifdef EXT4_INLINE_DATA_FL
 190     { EXT4_INLINE_DATA_FL, 'N', N_ ("Inode has inline data"), FALSE, FALSE },
 191 #endif
 192 #ifdef EXT4_PROJINHERIT_FL
 193     /* added in v1.43-WIP-2016-05-12
 194        ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07
 195                    97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */
 196     { EXT4_PROJINHERIT_FL, 'P', N_ ("Project hierarchy"), FALSE, FALSE },
 197 #endif
 198 #ifdef EXT4_VERITY_FL
 199     /* added in v1.44.4
 200        ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14
 201        v1.44.5
 202        ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */
 203     { EXT4_VERITY_FL, 'V', N_ ("Verity protected inode"), FALSE, FALSE },
 204 #endif
 205 };
 206 
 207 /* number of attributes */
 208 static const size_t check_attr_num = G_N_ELEMENTS (check_attr);
 209 
 210 /* modifiable attribute numbers */
 211 static int check_attr_mod[32];
 212 static int check_attr_mod_num = 0;  // 0..31
 213 
 214 /* maximum width of attribute text */
 215 static int check_attr_width = 0;
 216 
 217 static struct
 218 {
 219     int ret_cmd;
 220     button_flags_t flags;
 221     int width;
 222     const char *text;
 223     Widget *button;
 224 } chattr_but[BUTTONS] = {
 225     /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_ ("Set &all"), NULL },
 226     /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_ ("&Marked all"), NULL },
 227     /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_ ("S&et marked"), NULL },
 228     /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_ ("C&lear marked"), NULL },
 229     /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_ ("&Set"), NULL },
 230     /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_ ("&Cancel"), NULL },
 231 };
 232 
 233 static gboolean flags_changed;
 234 static int current_file;
 235 static gboolean ignore_all;
 236 
 237 static unsigned long and_mask, or_mask, flags;
 238 
 239 static WFileAttrText *file_attr;
 240 
 241 /* x-coord of widget in the dialog */
 242 static const int wx = 3;
 243 
 244 /* --------------------------------------------------------------------------------------------- */
 245 /*** file scope functions ************************************************************************/
 246 /* --------------------------------------------------------------------------------------------- */
 247 
 248 static inline gboolean
 249 chattr_is_modifiable (size_t i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 250 {
 251     return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0);
 252 }
 253 
 254 /* --------------------------------------------------------------------------------------------- */
 255 
 256 static void
 257 chattr_fill_str (unsigned long attr, char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 258 {
 259     size_t i;
 260 
 261     for (i = 0; i < check_attr_num; i++)
 262         str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-';
 263 
 264     str[check_attr_num] = '\0';
 265 }
 266 
 267 /* --------------------------------------------------------------------------------------------- */
 268 
 269 static void
 270 fileattrtext_fill (WFileAttrText *fat, unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 271 {
 272     chattr_fill_str (attr, fat->attrs);
 273     widget_draw (WIDGET (fat));
 274 }
 275 
 276 /* --------------------------------------------------------------------------------------------- */
 277 
 278 static cb_ret_t
 279 fileattrtext_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 280 {
 281     WFileAttrText *fat = (WFileAttrText *) w;
 282 
 283     switch (msg)
 284     {
 285     case MSG_DRAW:
 286     {
 287         int color = COLOR_NORMAL;
 288         size_t i;
 289 
 290         tty_setcolor (color);
 291 
 292         if (w->rect.cols > fat->filename_width)
 293         {
 294             widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2);
 295             tty_print_string (fat->filename);
 296         }
 297         else
 298         {
 299             widget_gotoyx (w, 0, 0);
 300             tty_print_string (str_trunc (fat->filename, w->rect.cols));
 301         }
 302 
 303         // hope that w->cols is greater than check_attr_num
 304         widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2);
 305         for (i = 0; i < check_attr_num; i++)
 306         {
 307             // Do not set new color for each symbol. Try to use previous color.
 308             if (chattr_is_modifiable (i))
 309             {
 310                 if (color == DISABLED_COLOR)
 311                 {
 312                     color = COLOR_NORMAL;
 313                     tty_setcolor (color);
 314                 }
 315             }
 316             else
 317             {
 318                 if (color != DISABLED_COLOR)
 319                 {
 320                     color = DISABLED_COLOR;
 321                     tty_setcolor (color);
 322                 }
 323             }
 324 
 325             tty_print_char (fat->attrs[i]);
 326         }
 327         return MSG_HANDLED;
 328     }
 329 
 330     case MSG_RESIZE:
 331     {
 332         const WRect *wo = &CONST_WIDGET (w->owner)->rect;
 333 
 334         widget_default_callback (w, sender, msg, parm, data);
 335         // initially file name may be wider than screen
 336         if (fat->filename_width > wo->cols - wx * 2)
 337         {
 338             w->rect.x = wo->x + wx;
 339             w->rect.cols = wo->cols - wx * 2;
 340         }
 341         return MSG_HANDLED;
 342     }
 343 
 344     case MSG_DESTROY:
 345         g_free (fat->filename);
 346         return MSG_HANDLED;
 347 
 348     default:
 349         return widget_default_callback (w, sender, msg, parm, data);
 350     }
 351 }
 352 
 353 /* --------------------------------------------------------------------------------------------- */
 354 
 355 static WFileAttrText *
 356 fileattrtext_new (int y, int x, const char *filename, unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 357 {
 358     WRect r = { y, x, 2, 1 };
 359     WFileAttrText *fat;
 360     int width;
 361 
 362     width = str_term_width1 (filename);
 363     r.cols = MAX (width, (int) check_attr_num);
 364 
 365     fat = g_new (WFileAttrText, 1);
 366     widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL);
 367 
 368     fat->filename = g_strdup (filename);
 369     fat->filename_width = width;
 370     fileattrtext_fill (fat, attr);
 371 
 372     return fat;
 373 }
 374 
 375 /* --------------------------------------------------------------------------------------------- */
 376 
 377 static void
 378 chattr_draw_select (const Widget *w, gboolean selected)
     /* [previous][next][first][last][top][bottom][index][help]  */
 379 {
 380     widget_gotoyx (w, 0, -1);
 381     tty_print_char (selected ? '*' : ' ');
 382     widget_gotoyx (w, 0, 1);
 383 }
 384 
 385 /* --------------------------------------------------------------------------------------------- */
 386 
 387 static void
 388 chattr_toggle_select (const WChattrBoxes *cb, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 389 {
 390     Widget *w;
 391 
 392     // find checkbox
 393     w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top));
 394 
 395     check_attr[Id].selected = !check_attr[Id].selected;
 396 
 397     tty_setcolor (COLOR_NORMAL);
 398     chattr_draw_select (w, check_attr[Id].selected);
 399 }
 400 
 401 /* --------------------------------------------------------------------------------------------- */
 402 
 403 static inline void
 404 chattrboxes_draw_scrollbar (const WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 405 {
 406     const Widget *w = CONST_WIDGET (cb);
 407     int max_line;
 408     int line;
 409     int i;
 410 
 411     // Are we at the top?
 412     widget_gotoyx (w, 0, w->rect.cols);
 413     if (cb->top == 0)
 414         tty_print_one_vline (TRUE);
 415     else
 416         tty_print_char ('^');
 417 
 418     max_line = w->rect.lines - 1;
 419 
 420     // Are we at the bottom?
 421     widget_gotoyx (w, max_line, w->rect.cols);
 422     if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num)
 423         tty_print_one_vline (TRUE);
 424     else
 425         tty_print_char ('v');
 426 
 427     // Now draw the nice relative pointer
 428     line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num;
 429 
 430     for (i = 1; i < max_line; i++)
 431     {
 432         widget_gotoyx (w, i, w->rect.cols);
 433         if (i != line)
 434             tty_print_one_vline (TRUE);
 435         else
 436             tty_print_char ('*');
 437     }
 438 }
 439 
 440 /* --------------------------------------------------------------------------------------------- */
 441 
 442 static void
 443 chattrboxes_draw (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 444 {
 445     Widget *w = WIDGET (cb);
 446     int i;
 447     GList *l;
 448     const int *colors;
 449 
 450     colors = widget_get_colors (w);
 451     tty_setcolor (colors[DLG_COLOR_NORMAL]);
 452     tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' ');
 453 
 454     // redraw checkboxes
 455     group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
 456 
 457     // draw scrollbar
 458     tty_setcolor (colors[DLG_COLOR_NORMAL]);
 459     if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines)
 460         chattrboxes_draw_scrollbar (cb);
 461 
 462     // mark selected checkboxes
 463     for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
 464         chattr_draw_select (WIDGET (l->data), check_attr[i].selected);
 465 }
 466 
 467 /* --------------------------------------------------------------------------------------------- */
 468 
 469 static void
 470 chattrboxes_rename (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 471 {
 472     Widget *w = WIDGET (cb);
 473     gboolean active;
 474     int i;
 475     GList *l;
 476 
 477     active = widget_get_state (w, WST_ACTIVE);
 478 
 479     // lock the group to avoid redraw of checkboxes individually
 480     if (active)
 481         widget_set_state (w, WST_SUSPENDED, TRUE);
 482 
 483     for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
 484     {
 485         WCheck *c = CHECK (l->data);
 486         int m = check_attr_mod[i];
 487         char btext[BUF_SMALL];  // FIXME: are 128 bytes enough?
 488 
 489         g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text);
 490         check_set_text (c, btext);
 491         c->state = check_attr[m].state;
 492     }
 493 
 494     // unlock
 495     if (active)
 496         widget_set_state (w, WST_ACTIVE, TRUE);
 497 
 498     widget_draw (w);
 499 }
 500 
 501 /* --------------------------------------------------------------------------------------------- */
 502 
 503 static void
 504 checkboxes_save_state (const WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 505 {
 506     int i;
 507     GList *l;
 508 
 509     for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
 510     {
 511         int m = check_attr_mod[i];
 512 
 513         check_attr[m].state = CHECK (l->data)->state;
 514     }
 515 }
 516 
 517 /* --------------------------------------------------------------------------------------------- */
 518 
 519 static cb_ret_t
 520 chattrboxes_down (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 521 {
 522     if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1)
 523     {
 524         /* We are on the last checkbox.
 525            Keep this position. */
 526 
 527         if (cb->pos == check_attr_mod_num - 1)
 528             // get out of widget
 529             return MSG_NOT_HANDLED;
 530 
 531         // emulate scroll of checkboxes
 532         checkboxes_save_state (cb);
 533         cb->pos++;
 534         cb->top++;
 535         chattrboxes_rename (cb);
 536     }
 537     else  // cb->pos > cb-top
 538     {
 539         GList *l;
 540 
 541         // select next checkbox
 542         cb->pos++;
 543         l = g_list_next (GROUP (cb)->current);
 544         widget_select (WIDGET (l->data));
 545     }
 546 
 547     return MSG_HANDLED;
 548 }
 549 
 550 /* --------------------------------------------------------------------------------------------- */
 551 
 552 static cb_ret_t
 553 chattrboxes_page_down (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 554 {
 555     WGroup *g = GROUP (cb);
 556     GList *l;
 557 
 558     if (cb->pos == check_attr_mod_num - 1)
 559     {
 560         /* We are on the last checkbox.
 561            Keep this position.
 562            Do nothing. */
 563         l = g_list_last (g->widgets);
 564     }
 565     else
 566     {
 567         int i = WIDGET (cb)->rect.lines;
 568 
 569         checkboxes_save_state (cb);
 570 
 571         if (cb->top > check_attr_mod_num - 2 * i)
 572             i = check_attr_mod_num - i - cb->top;
 573         if (cb->top + i < 0)
 574             i = -cb->top;
 575         if (i == 0)
 576         {
 577             cb->pos = check_attr_mod_num - 1;
 578             cb->top += i;
 579             l = g_list_last (g->widgets);
 580         }
 581         else
 582         {
 583             cb->pos += i;
 584             cb->top += i;
 585             l = g_list_nth (g->widgets, cb->pos - cb->top);
 586         }
 587 
 588         chattrboxes_rename (cb);
 589     }
 590 
 591     widget_select (WIDGET (l->data));
 592 
 593     return MSG_HANDLED;
 594 }
 595 
 596 /* --------------------------------------------------------------------------------------------- */
 597 
 598 static cb_ret_t
 599 chattrboxes_end (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 600 {
 601     GList *l;
 602 
 603     checkboxes_save_state (cb);
 604     cb->pos = check_attr_mod_num - 1;
 605     cb->top = cb->pos - WIDGET (cb)->rect.lines + 1;
 606     l = g_list_last (GROUP (cb)->widgets);
 607     chattrboxes_rename (cb);
 608     widget_select (WIDGET (l->data));
 609 
 610     return MSG_HANDLED;
 611 }
 612 
 613 /* --------------------------------------------------------------------------------------------- */
 614 
 615 static cb_ret_t
 616 chattrboxes_up (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 617 {
 618     if (cb->pos == cb->top)
 619     {
 620         /* We are on the first checkbox.
 621            Keep this position. */
 622 
 623         if (cb->top == 0)
 624             // get out of widget
 625             return MSG_NOT_HANDLED;
 626 
 627         // emulate scroll of checkboxes
 628         checkboxes_save_state (cb);
 629         cb->pos--;
 630         cb->top--;
 631         chattrboxes_rename (cb);
 632     }
 633     else  // cb->pos > cb-top
 634     {
 635         GList *l;
 636 
 637         // select previous checkbox
 638         cb->pos--;
 639         l = g_list_previous (GROUP (cb)->current);
 640         widget_select (WIDGET (l->data));
 641     }
 642 
 643     return MSG_HANDLED;
 644 }
 645 
 646 /* --------------------------------------------------------------------------------------------- */
 647 
 648 static cb_ret_t
 649 chattrboxes_page_up (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 650 {
 651     WGroup *g = GROUP (cb);
 652     GList *l;
 653 
 654     if (cb->pos == 0 && cb->top == 0)
 655     {
 656         /* We are on the first checkbox.
 657            Keep this position.
 658            Do nothing. */
 659         l = g_list_first (g->widgets);
 660     }
 661     else
 662     {
 663         int i = WIDGET (cb)->rect.lines;
 664 
 665         checkboxes_save_state (cb);
 666 
 667         if (cb->top < i)
 668             i = cb->top;
 669         if (i == 0)
 670         {
 671             cb->pos = 0;
 672             cb->top -= i;
 673             l = g_list_first (g->widgets);
 674         }
 675         else
 676         {
 677             cb->pos -= i;
 678             cb->top -= i;
 679             l = g_list_nth (g->widgets, cb->pos - cb->top);
 680         }
 681 
 682         chattrboxes_rename (cb);
 683     }
 684 
 685     widget_select (WIDGET (l->data));
 686 
 687     return MSG_HANDLED;
 688 }
 689 
 690 /* --------------------------------------------------------------------------------------------- */
 691 
 692 static cb_ret_t
 693 chattrboxes_home (WChattrBoxes *cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 694 {
 695     GList *l;
 696 
 697     checkboxes_save_state (cb);
 698     cb->pos = 0;
 699     cb->top = 0;
 700     l = g_list_first (GROUP (cb)->widgets);
 701     chattrboxes_rename (cb);
 702     widget_select (WIDGET (l->data));
 703 
 704     return MSG_HANDLED;
 705 }
 706 
 707 /* --------------------------------------------------------------------------------------------- */
 708 
 709 static cb_ret_t
 710 chattrboxes_execute_cmd (WChattrBoxes *cb, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 711 {
 712     switch (command)
 713     {
 714     case CK_Down:
 715         return chattrboxes_down (cb);
 716 
 717     case CK_PageDown:
 718         return chattrboxes_page_down (cb);
 719 
 720     case CK_Bottom:
 721         return chattrboxes_end (cb);
 722 
 723     case CK_Up:
 724         return chattrboxes_up (cb);
 725 
 726     case CK_PageUp:
 727         return chattrboxes_page_up (cb);
 728 
 729     case CK_Top:
 730         return chattrboxes_home (cb);
 731 
 732     case CK_Mark:
 733     case CK_MarkAndDown:
 734     {
 735         chattr_toggle_select (cb, cb->pos);  // FIXME
 736         if (command == CK_MarkAndDown)
 737             chattrboxes_down (cb);
 738 
 739         return MSG_HANDLED;
 740     }
 741 
 742     default:
 743         return MSG_NOT_HANDLED;
 744     }
 745 }
 746 
 747 /* --------------------------------------------------------------------------------------------- */
 748 
 749 static cb_ret_t
 750 chattrboxes_key (WChattrBoxes *cb, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 751 {
 752     long command;
 753 
 754     command = widget_lookup_key (WIDGET (cb), key);
 755     if (command == CK_IgnoreKey)
 756         return MSG_NOT_HANDLED;
 757     return chattrboxes_execute_cmd (cb, command);
 758 }
 759 
 760 /* --------------------------------------------------------------------------------------------- */
 761 
 762 static cb_ret_t
 763 chattrboxes_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 764 {
 765     WChattrBoxes *cb = CHATTRBOXES (w);
 766     WGroup *g = GROUP (w);
 767 
 768     switch (msg)
 769     {
 770     case MSG_DRAW:
 771         chattrboxes_draw (cb);
 772         return MSG_HANDLED;
 773 
 774     case MSG_NOTIFY:
 775     {
 776         // handle checkboxes
 777         int i;
 778 
 779         i = g_list_index (g->widgets, sender);
 780         if (i >= 0)
 781         {
 782             int m;
 783 
 784             i += cb->top;
 785             m = check_attr_mod[i];
 786             flags ^= check_attr[m].flags;
 787             fileattrtext_fill (file_attr, flags);
 788             chattr_toggle_select (cb, i);
 789             flags_changed = TRUE;
 790             return MSG_HANDLED;
 791         }
 792     }
 793         return MSG_NOT_HANDLED;
 794 
 795     case MSG_CHANGED_FOCUS:
 796         // sender is one of chattr checkboxes
 797         if (widget_get_state (sender, WST_FOCUSED))
 798         {
 799             int i;
 800 
 801             i = g_list_index (g->widgets, sender);
 802             cb->pos = cb->top + i;
 803         }
 804         return MSG_HANDLED;
 805 
 806     case MSG_KEY:
 807     {
 808         cb_ret_t ret;
 809 
 810         ret = chattrboxes_key (cb, parm);
 811         if (ret != MSG_HANDLED)
 812             ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL);
 813 
 814         return ret;
 815     }
 816 
 817     case MSG_ACTION:
 818         return chattrboxes_execute_cmd (cb, parm);
 819 
 820     case MSG_DESTROY:
 821         // save all states
 822         checkboxes_save_state (cb);
 823         MC_FALLTHROUGH;
 824 
 825     default:
 826         return group_default_callback (w, sender, msg, parm, data);
 827     }
 828 }
 829 
 830 /* --------------------------------------------------------------------------------------------- */
 831 
 832 static int
 833 chattrboxes_handle_mouse_event (Widget *w, Gpm_Event *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 834 {
 835     int mou;
 836 
 837     mou = mouse_handle_event (w, event);
 838     if (mou == MOU_UNHANDLED)
 839         mou = group_handle_mouse_event (w, event);
 840 
 841     return mou;
 842 }
 843 
 844 /* --------------------------------------------------------------------------------------------- */
 845 
 846 static void
 847 chattrboxes_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 848 {
 849     WChattrBoxes *cb = CHATTRBOXES (w);
 850 
 851     (void) event;
 852 
 853     switch (msg)
 854     {
 855     case MSG_MOUSE_SCROLL_UP:
 856         chattrboxes_up (cb);
 857         break;
 858 
 859     case MSG_MOUSE_SCROLL_DOWN:
 860         chattrboxes_down (cb);
 861         break;
 862 
 863     default:
 864         // return MOU_UNHANDLED
 865         event->result.abort = TRUE;
 866         break;
 867     }
 868 }
 869 
 870 /* --------------------------------------------------------------------------------------------- */
 871 
 872 static WChattrBoxes *
 873 chattrboxes_new (const WRect *r)
     /* [previous][next][first][last][top][bottom][index][help]  */
 874 {
 875     WChattrBoxes *cb;
 876     Widget *w;
 877     WGroup *cbg;
 878     int i;
 879 
 880     cb = g_new0 (WChattrBoxes, 1);
 881     w = WIDGET (cb);
 882     cbg = GROUP (cb);
 883     group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback);
 884     w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
 885     w->mouse_handler = chattrboxes_handle_mouse_event;
 886     w->keymap = chattr_map;
 887 
 888     // create checkboxes
 889     for (i = 0; i < r->lines; i++)
 890     {
 891         int m = check_attr_mod[i];
 892         WCheck *check;
 893 
 894         check = check_new (i, 0, check_attr[m].state, NULL);
 895         group_add_widget (cbg, check);
 896     }
 897 
 898     chattrboxes_rename (cb);
 899 
 900     // select first checkbox
 901     cbg->current = cbg->widgets;
 902 
 903     return cb;
 904 }
 905 
 906 /* --------------------------------------------------------------------------------------------- */
 907 
 908 static void
 909 chattr_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 910 {
 911     static gboolean i18n = FALSE;
 912     size_t i;
 913 
 914     for (i = 0; i < check_attr_num; i++)
 915         check_attr[i].selected = FALSE;
 916 
 917     if (i18n)
 918         return;
 919 
 920     i18n = TRUE;
 921 
 922     for (i = 0; i < check_attr_num; i++)
 923         if (chattr_is_modifiable (i))
 924         {
 925             int width;
 926 
 927 #ifdef ENABLE_NLS
 928             check_attr[i].text = _ (check_attr[i].text);
 929 #endif
 930 
 931             check_attr_mod[check_attr_mod_num++] = i;
 932 
 933             width = 4 + str_term_width1 (check_attr[i].text);  // "(Q) text "
 934             check_attr_width = MAX (check_attr_width, width);
 935         }
 936 
 937     check_attr_width += 1 + 3 + 1;  // mark, [x] and space
 938 
 939     for (i = 0; i < BUTTONS; i++)
 940     {
 941 #ifdef ENABLE_NLS
 942         chattr_but[i].text = _ (chattr_but[i].text);
 943 #endif
 944 
 945         chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3;  // [], spaces and w/o &
 946         if (chattr_but[i].flags == DEFPUSH_BUTTON)
 947             chattr_but[i].width += 2;  // <>
 948     }
 949 }
 950 
 951 /* --------------------------------------------------------------------------------------------- */
 952 
 953 static WDialog *
 954 chattr_dlg_create (WPanel *panel, const char *fname, unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 955 {
 956     Widget *mw = WIDGET (WIDGET (panel)->owner);
 957     gboolean single_set;
 958     WDialog *ch_dlg;
 959     int lines, cols;
 960     int checkboxes_lines = check_attr_mod_num;
 961     size_t i;
 962     int y;
 963     Widget *dw;
 964     WGroup *dg;
 965     WChattrBoxes *cb;
 966     const int cb_scrollbar_width = 1;
 967     WRect r;
 968 
 969     // prepare to set up checkbox states
 970     for (i = 0; i < check_attr_num; i++)
 971         check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0;
 972 
 973     cols = check_attr_width + cb_scrollbar_width;
 974 
 975     single_set = (panel->marked < 2);
 976 
 977     lines = 5 + checkboxes_lines + 4;
 978     if (!single_set)
 979         lines += 3;
 980 
 981     if (lines >= mw->rect.lines - 2)
 982     {
 983         int dl;
 984 
 985         dl = lines - (mw->rect.lines - 2);
 986         lines -= dl;
 987         checkboxes_lines -= dl;
 988     }
 989 
 990     ch_dlg = dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors,
 991                          dlg_default_callback, NULL, "[Chattr]", _ ("Chattr command"));
 992     dg = GROUP (ch_dlg);
 993     dw = WIDGET (ch_dlg);
 994 
 995     y = 2;
 996     file_attr = fileattrtext_new (y, wx, fname, attr);
 997     group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
 998     y += WIDGET (file_attr)->rect.lines;
 999     group_add_widget (dg, hline_new (y++, -1, -1));
1000 
1001     if (cols < WIDGET (file_attr)->rect.cols)
1002     {
1003         r = dw->rect;
1004         cols = WIDGET (file_attr)->rect.cols;
1005         cols = MIN (cols, mw->rect.cols - wx * 2);
1006         r.cols = cols + wx * 2;
1007         r.lines = lines;
1008         widget_set_size_rect (dw, &r);
1009     }
1010 
1011     checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines);
1012     rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols);
1013     cb = chattrboxes_new (&r);
1014     group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
1015 
1016     y += checkboxes_lines - 1;
1017     cols = 0;
1018 
1019     for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
1020     {
1021         if (i == 0 || i == BUTTONS - 2)
1022             group_add_widget (dg, hline_new (y++, -1, -1));
1023 
1024         chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width,
1025                                                    chattr_but[i].ret_cmd, chattr_but[i].flags,
1026                                                    chattr_but[i].text, NULL));
1027         group_add_widget (dg, chattr_but[i].button);
1028 
1029         i++;
1030         chattr_but[i].button =
1031             WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd,
1032                                 chattr_but[i].flags, chattr_but[i].text, NULL));
1033         group_add_widget (dg, chattr_but[i].button);
1034 
1035         // two buttons in a row
1036         cols =
1037             MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols);
1038     }
1039 
1040     // adjust dialog size and button positions
1041     cols += 6;
1042     if (cols > dw->rect.cols)
1043     {
1044         r = dw->rect;
1045         r.lines = lines;
1046         r.cols = cols;
1047         widget_set_size_rect (dw, &r);
1048 
1049         // dialog center
1050         cols = dw->rect.x + dw->rect.cols / 2 + 1;
1051 
1052         for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
1053         {
1054             Widget *b;
1055 
1056             b = chattr_but[i++].button;
1057             r = b->rect;
1058             r.x = cols - r.cols;
1059             widget_set_size_rect (b, &r);
1060 
1061             b = chattr_but[i].button;
1062             r = b->rect;
1063             r.x = cols + 1;
1064             widget_set_size_rect (b, &r);
1065         }
1066     }
1067 
1068     widget_select (WIDGET (cb));
1069 
1070     return ch_dlg;
1071 }
1072 
1073 /* --------------------------------------------------------------------------------------------- */
1074 
1075 static void
1076 chattr_done (gboolean need_update)
     /* [previous][next][first][last][top][bottom][index][help]  */
1077 {
1078     if (need_update)
1079         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
1080     repaint_screen ();
1081 }
1082 
1083 /* --------------------------------------------------------------------------------------------- */
1084 
1085 static gboolean
1086 try_chattr (const vfs_path_t *p, unsigned long m)
     /* [previous][next][first][last][top][bottom][index][help]  */
1087 {
1088     const char *fname = NULL;
1089 
1090     while (mc_fsetflags (p, m) == -1 && !ignore_all)
1091     {
1092         int my_errno = errno;
1093         int result;
1094         char *msg;
1095 
1096         if (fname == NULL)
1097             fname = x_basename (vfs_path_as_str (p));
1098         msg = g_strdup_printf (_ ("Cannot chattr \"%s\"\n%s"), fname, unix_error_string (my_errno));
1099         result = query_dialog (MSG_ERROR, msg, D_ERROR, 4, _ ("&Ignore"), _ ("Ignore &all"),
1100                                _ ("&Retry"), _ ("&Cancel"));
1101         g_free (msg);
1102 
1103         switch (result)
1104         {
1105         case 0:
1106             // try next file
1107             return TRUE;
1108 
1109         case 1:
1110             ignore_all = TRUE;
1111             // try next file
1112             return TRUE;
1113 
1114         case 2:
1115             // retry this file
1116             break;
1117 
1118         case 3:
1119         default:
1120             // stop remain files processing
1121             return FALSE;
1122         }
1123     }
1124 
1125     return TRUE;
1126 }
1127 
1128 /* --------------------------------------------------------------------------------------------- */
1129 
1130 static gboolean
1131 do_chattr (WPanel *panel, const vfs_path_t *p, unsigned long m)
     /* [previous][next][first][last][top][bottom][index][help]  */
1132 {
1133     gboolean ret;
1134 
1135     m &= and_mask;
1136     m |= or_mask;
1137 
1138     ret = try_chattr (p, m);
1139 
1140     do_file_mark (panel, current_file, 0);
1141 
1142     return ret;
1143 }
1144 
1145 /* --------------------------------------------------------------------------------------------- */
1146 
1147 static void
1148 chattr_apply_mask (WPanel *panel, vfs_path_t *vpath, unsigned long m)
     /* [previous][next][first][last][top][bottom][index][help]  */
1149 {
1150     gboolean ok;
1151 
1152     if (!do_chattr (panel, vpath, m))
1153         return;
1154 
1155     do
1156     {
1157         const GString *fname;
1158 
1159         fname = panel_find_marked_file (panel, &current_file);
1160         vpath = vfs_path_from_str (fname->str);
1161         ok = (mc_fgetflags (vpath, &m) == 0);
1162 
1163         if (!ok)
1164         {
1165             // if current file was deleted outside mc -- try next file
1166             // decrease panel->marked
1167             do_file_mark (panel, current_file, 0);
1168 
1169             // try next file
1170             ok = TRUE;
1171         }
1172         else
1173         {
1174             flags = m;
1175             ok = do_chattr (panel, vpath, m);
1176             vfs_path_free (vpath, TRUE);
1177         }
1178     }
1179     while (ok && panel->marked != 0);
1180 }
1181 
1182 /* --------------------------------------------------------------------------------------------- */
1183 /*** public functions ****************************************************************************/
1184 /* --------------------------------------------------------------------------------------------- */
1185 
1186 void
1187 chattr_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1188 {
1189     gboolean need_update = FALSE;
1190     gboolean end_chattr = FALSE;
1191 
1192     chattr_init ();
1193 
1194     current_file = 0;
1195     ignore_all = FALSE;
1196 
1197     do
1198     {  // do while any files remaining
1199         vfs_path_t *vpath;
1200         WDialog *ch_dlg;
1201         const GString *fname;
1202         size_t i;
1203         int result;
1204 
1205         do_refresh ();
1206 
1207         need_update = FALSE;
1208         end_chattr = FALSE;
1209 
1210         fname = panel_get_marked_file (panel, &current_file);
1211         if (fname == NULL)
1212             break;
1213 
1214         vpath = vfs_path_from_str (fname->str);
1215 
1216         if (mc_fgetflags (vpath, &flags) != 0)
1217         {
1218             message (D_ERROR, MSG_ERROR, _ ("Cannot get ext2 attributes of \"%s\"\n%s"), fname->str,
1219                      unix_error_string (errno));
1220             vfs_path_free (vpath, TRUE);
1221             break;
1222         }
1223 
1224         flags_changed = FALSE;
1225 
1226         ch_dlg = chattr_dlg_create (panel, fname->str, flags);
1227         result = dlg_run (ch_dlg);
1228         widget_destroy (WIDGET (ch_dlg));
1229 
1230         switch (result)
1231         {
1232         case B_CANCEL:
1233             end_chattr = TRUE;
1234             break;
1235 
1236         case B_ENTER:
1237             if (flags_changed)
1238             {
1239                 if (panel->marked <= 1)
1240                 {
1241                     // single or last file
1242                     if (mc_fsetflags (vpath, flags) == -1 && !ignore_all)
1243                         message (D_ERROR, MSG_ERROR, _ ("Cannot chattr \"%s\"\n%s"), fname->str,
1244                                  unix_error_string (errno));
1245                     end_chattr = TRUE;
1246                 }
1247                 else if (!try_chattr (vpath, flags))
1248                 {
1249                     // stop multiple files processing
1250                     result = B_CANCEL;
1251                     end_chattr = TRUE;
1252                 }
1253             }
1254 
1255             need_update = TRUE;
1256             break;
1257 
1258         case B_SETALL:
1259         case B_MARKED:
1260             or_mask = 0;
1261             and_mask = ~0;
1262 
1263             for (i = 0; i < check_attr_num; i++)
1264                 if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL))
1265                 {
1266                     if (check_attr[i].state)
1267                         or_mask |= check_attr[i].flags;
1268                     else
1269                         and_mask &= ~check_attr[i].flags;
1270                 }
1271 
1272             chattr_apply_mask (panel, vpath, flags);
1273             need_update = TRUE;
1274             end_chattr = TRUE;
1275             break;
1276 
1277         case B_SETMRK:
1278             or_mask = 0;
1279             and_mask = ~0;
1280 
1281             for (i = 0; i < check_attr_num; i++)
1282                 if (chattr_is_modifiable (i) && check_attr[i].selected)
1283                     or_mask |= check_attr[i].flags;
1284 
1285             chattr_apply_mask (panel, vpath, flags);
1286             need_update = TRUE;
1287             end_chattr = TRUE;
1288             break;
1289 
1290         case B_CLRMRK:
1291             or_mask = 0;
1292             and_mask = ~0;
1293 
1294             for (i = 0; i < check_attr_num; i++)
1295                 if (chattr_is_modifiable (i) && check_attr[i].selected)
1296                     and_mask &= ~check_attr[i].flags;
1297 
1298             chattr_apply_mask (panel, vpath, flags);
1299             need_update = TRUE;
1300             end_chattr = TRUE;
1301             break;
1302 
1303         default:
1304             break;
1305         }
1306 
1307         if (panel->marked != 0 && result != B_CANCEL)
1308         {
1309             do_file_mark (panel, current_file, 0);
1310             need_update = TRUE;
1311         }
1312 
1313         vfs_path_free (vpath, TRUE);
1314     }
1315     while (panel->marked != 0 && !end_chattr);
1316 
1317     chattr_done (need_update);
1318 }
1319 
1320 /* --------------------------------------------------------------------------------------------- */
1321 
1322 const char *
1323 chattr_get_as_str (unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
1324 {
1325     static char str[32 + 1];  // 32 bits in attributes (unsigned long)
1326 
1327     chattr_fill_str (attr, str);
1328 
1329     return str;
1330 }
1331 
1332 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */