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