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. next_file
  28. try_chattr
  29. do_chattr
  30. chattr_apply_mask
  31. chattr_cmd
  32. chattr_get_as_str

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

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