Manual pages: mcmcdiffmceditmcview

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

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