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