2 Chmod command -- for the Midnight Commander
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 This file is part of the Midnight Commander.
9 The Midnight Commander is free software: you can redistribute it
10 and/or modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation, either version 3 of the License,
12 or (at your option) any later version.
14 The Midnight Commander is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 * \brief Source: chmod command
30 #include <sys/types.h>
34 #include "lib/global.h"
36 #include "lib/tty/tty.h"
38 #include "lib/vfs/vfs.h"
39 #include "lib/strutil.h"
41 #include "lib/widget.h"
43 #include "cmd.h" /* chmod_cmd() */
45 /*** global variables ****************************************************************************/
47 /*** file scope macro definitions ****************************************************************/
52 #define B_MARKED B_USER
53 #define B_SETALL (B_USER + 1)
54 #define B_SETMRK (B_USER + 2)
55 #define B_CLRMRK (B_USER + 3)
58 #define BUTTONS_PERM 12
61 /*** file scope type declarations ****************************************************************/
63 /*** forward declarations (file scope functions) *************************************************/
65 /*** file scope variables ************************************************************************/
73 } check_perm
[BUTTONS_PERM
] = {
75 { S_ISUID
, N_("set &user ID on execution"), FALSE
, NULL
},
76 { S_ISGID
, N_("set &group ID on execution"), FALSE
, NULL
},
77 { S_ISVTX
, N_("stick&y bit"), FALSE
, NULL
},
78 { S_IRUSR
, N_("&read by owner"), FALSE
, NULL
},
79 { S_IWUSR
, N_("&write by owner"), FALSE
, NULL
},
80 { S_IXUSR
, N_("e&xecute/search by owner"), FALSE
, NULL
},
81 { S_IRGRP
, N_("rea&d by group"), FALSE
, NULL
},
82 { S_IWGRP
, N_("write by grou&p"), FALSE
, NULL
},
83 { S_IXGRP
, N_("execu&te/search by group"), FALSE
, NULL
},
84 { S_IROTH
, N_("read &by others"), FALSE
, NULL
},
85 { S_IWOTH
, N_("wr&ite by others"), FALSE
, NULL
},
86 { S_IXOTH
, N_("execute/searc&h by others"), FALSE
, NULL
}
90 static int check_perm_len
= 0;
92 static const char *file_info_labels
[LABELS
] = {
94 N_("Permissions (octal):"),
99 static int file_info_labels_len
= 0;
104 button_flags_t flags
;
105 int y
; /* vertical position relatively to dialog bottom boundary */
108 } chmod_but
[BUTTONS
] = {
110 { B_SETALL
, NORMAL_BUTTON
, 6, 0, N_("Set &all") },
111 { B_MARKED
, NORMAL_BUTTON
, 6, 0, N_("&Marked all") },
112 { B_SETMRK
, NORMAL_BUTTON
, 5, 0, N_("S&et marked") },
113 { B_CLRMRK
, NORMAL_BUTTON
, 5, 0, N_("C&lear marked") },
114 { B_ENTER
, DEFPUSH_BUTTON
, 3, 0, N_("&Set") },
115 { B_CANCEL
, NORMAL_BUTTON
, 3, 0, N_("&Cancel") }
119 static gboolean mode_change
;
120 static int current_file
;
121 static gboolean ignore_all
;
123 static mode_t and_mask
, or_mask
, ch_mode
;
125 static WLabel
*statl
;
126 static WGroupbox
*file_gb
;
128 /* --------------------------------------------------------------------------------------------- */
129 /*** file scope functions ************************************************************************/
130 /* --------------------------------------------------------------------------------------------- */
135 static gboolean i18n
= FALSE
;
138 for (i
= 0; i
< BUTTONS_PERM
; i
++)
139 check_perm
[i
].selected
= FALSE
;
147 for (i
= 0; i
< BUTTONS_PERM
; i
++)
148 check_perm
[i
].text
= _(check_perm
[i
].text
);
150 for (i
= 0; i
< LABELS
; i
++)
151 file_info_labels
[i
] = _(file_info_labels
[i
]);
153 for (i
= 0; i
< BUTTONS
; i
++)
154 chmod_but
[i
].text
= _(chmod_but
[i
].text
);
155 #endif /* ENABLE_NLS */
157 for (i
= 0; i
< BUTTONS_PERM
; i
++)
159 len
= str_term_width1 (check_perm
[i
].text
);
160 check_perm_len
= MAX (check_perm_len
, len
);
163 check_perm_len
+= 1 + 3 + 1; /* mark, [x] and space */
165 for (i
= 0; i
< LABELS
; i
++)
167 len
= str_term_width1 (file_info_labels
[i
]) + 2; /* spaces around */
168 file_info_labels_len
= MAX (file_info_labels_len
, len
);
171 for (i
= 0; i
< BUTTONS
; i
++)
173 chmod_but
[i
].len
= str_term_width1 (chmod_but
[i
].text
) + 3; /* [], spaces and w/o & */
174 if (chmod_but
[i
].flags
== DEFPUSH_BUTTON
)
175 chmod_but
[i
].len
+= 2; /* <> */
179 /* --------------------------------------------------------------------------------------------- */
182 chmod_draw_select (const WDialog
*h
, int Id
)
184 widget_gotoyx (h
, PY
+ Id
+ 1, PX
+ 1);
185 tty_print_char (check_perm
[Id
].selected
? '*' : ' ');
186 widget_gotoyx (h
, PY
+ Id
+ 1, PX
+ 3);
189 /* --------------------------------------------------------------------------------------------- */
192 chmod_toggle_select (const WDialog
*h
, int Id
)
194 check_perm
[Id
].selected
= !check_perm
[Id
].selected
;
195 tty_setcolor (COLOR_NORMAL
);
196 chmod_draw_select (h
, Id
);
199 /* --------------------------------------------------------------------------------------------- */
202 chmod_refresh (const WDialog
*h
)
207 tty_setcolor (COLOR_NORMAL
);
209 for (i
= 0; i
< BUTTONS_PERM
; i
++)
210 chmod_draw_select (h
, i
);
212 y
= WIDGET (file_gb
)->rect
.y
+ 1;
213 x
= WIDGET (file_gb
)->rect
.x
+ 2;
216 tty_print_string (file_info_labels
[0]);
217 tty_gotoyx (y
+ 2, x
);
218 tty_print_string (file_info_labels
[1]);
219 tty_gotoyx (y
+ 4, x
);
220 tty_print_string (file_info_labels
[2]);
221 tty_gotoyx (y
+ 6, x
);
222 tty_print_string (file_info_labels
[3]);
225 /* --------------------------------------------------------------------------------------------- */
228 chmod_bg_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
233 frame_callback (w
, NULL
, MSG_DRAW
, 0, NULL
);
234 chmod_refresh (CONST_DIALOG (w
->owner
));
238 return frame_callback (w
, sender
, msg
, parm
, data
);
242 /* --------------------------------------------------------------------------------------------- */
245 chmod_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
247 WGroup
*g
= GROUP (w
);
248 WDialog
*h
= DIALOG (w
);
254 /* handle checkboxes */
257 /* whether notification was sent by checkbox? */
258 for (i
= 0; i
< BUTTONS_PERM
; i
++)
259 if (sender
== WIDGET (check_perm
[i
].check
))
262 if (i
< BUTTONS_PERM
)
264 ch_mode
^= check_perm
[i
].mode
;
265 label_set_textv (statl
, "%o", (unsigned int) ch_mode
);
266 chmod_toggle_select (h
, i
);
272 return MSG_NOT_HANDLED
;
275 if (parm
== 'T' || parm
== 't' || parm
== KEY_IC
)
280 id
= group_get_current_widget_id (g
);
281 for (i
= 0; i
< BUTTONS_PERM
; i
++)
282 if (id
== WIDGET (check_perm
[i
].check
)->id
)
285 if (i
< BUTTONS_PERM
)
287 chmod_toggle_select (h
, i
);
289 group_select_next_widget (g
);
293 return MSG_NOT_HANDLED
;
296 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
300 /* --------------------------------------------------------------------------------------------- */
303 chmod_dlg_create (WPanel
*panel
, const char *fname
, const struct stat
*sf_stat
)
312 const char *c_fname
, *c_fown
, *c_fgrp
;
313 char buffer
[BUF_TINY
];
317 single_set
= (panel
->marked
< 2);
318 perm_gb_len
= check_perm_len
+ 2;
319 file_gb_len
= file_info_labels_len
+ 2;
320 cols
= str_term_width1 (fname
) + 2 + 1;
321 file_gb_len
= MAX (file_gb_len
, cols
);
323 lines
= single_set
? 20 : 23;
324 cols
= perm_gb_len
+ file_gb_len
+ 1 + 6;
328 /* shrink the right groupbox */
330 file_gb_len
= cols
- (perm_gb_len
+ 1 + 6);
334 dlg_create (TRUE
, 0, 0, lines
, cols
, WPOS_CENTER
, FALSE
, dialog_colors
,
335 chmod_callback
, NULL
, "[Chmod]", _("Chmod command"));
338 /* draw background */
339 ch_dlg
->bg
->callback
= chmod_bg_callback
;
341 group_add_widget (g
, groupbox_new (PY
, PX
, BUTTONS_PERM
+ 2, perm_gb_len
, _("Permission")));
343 for (i
= 0; i
< BUTTONS_PERM
; i
++)
345 check_perm
[i
].check
= check_new (PY
+ i
+ 1, PX
+ 2, (ch_mode
& check_perm
[i
].mode
) != 0,
347 group_add_widget (g
, check_perm
[i
].check
);
350 file_gb
= groupbox_new (PY
, PX
+ perm_gb_len
+ 1, BUTTONS_PERM
+ 2, file_gb_len
, _("File"));
351 group_add_widget (g
, file_gb
);
355 cols
= PX
+ perm_gb_len
+ 3;
356 c_fname
= str_trunc (fname
, file_gb_len
- 3);
357 group_add_widget (g
, label_new (y
, cols
, c_fname
));
358 g_snprintf (buffer
, sizeof (buffer
), "%o", (unsigned int) ch_mode
);
359 statl
= label_new (y
+ 2, cols
, buffer
);
360 group_add_widget (g
, statl
);
361 c_fown
= str_trunc (get_owner (sf_stat
->st_uid
), file_gb_len
- 3);
362 group_add_widget (g
, label_new (y
+ 4, cols
, c_fown
));
363 c_fgrp
= str_trunc (get_group (sf_stat
->st_gid
), file_gb_len
- 3);
364 group_add_widget (g
, label_new (y
+ 6, cols
, c_fgrp
));
370 group_add_widget (g
, hline_new (lines
- chmod_but
[i
].y
- 1, -1, -1));
372 for (; i
< BUTTONS
- 2; i
++)
374 y
= lines
- chmod_but
[i
].y
;
375 group_add_widget (g
, button_new (y
, WIDGET (ch_dlg
)->rect
.cols
/ 2 - chmod_but
[i
].len
,
376 chmod_but
[i
].ret_cmd
, chmod_but
[i
].flags
,
377 chmod_but
[i
].text
, NULL
));
379 group_add_widget (g
, button_new (y
, WIDGET (ch_dlg
)->rect
.cols
/ 2 + 1,
380 chmod_but
[i
].ret_cmd
, chmod_but
[i
].flags
,
381 chmod_but
[i
].text
, NULL
));
386 y
= lines
- chmod_but
[i
].y
;
387 group_add_widget (g
, hline_new (y
- 1, -1, -1));
388 group_add_widget (g
, button_new (y
, WIDGET (ch_dlg
)->rect
.cols
/ 2 - chmod_but
[i
].len
,
389 chmod_but
[i
].ret_cmd
, chmod_but
[i
].flags
, chmod_but
[i
].text
,
392 group_add_widget (g
, button_new (y
, WIDGET (ch_dlg
)->rect
.cols
/ 2 + 1, chmod_but
[i
].ret_cmd
,
393 chmod_but
[i
].flags
, chmod_but
[i
].text
, NULL
));
395 /* select first checkbox */
396 widget_select (WIDGET (check_perm
[0].check
));
401 /* --------------------------------------------------------------------------------------------- */
404 chmod_done (gboolean need_update
)
407 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
411 /* --------------------------------------------------------------------------------------------- */
413 static const GString
*
414 next_file (const WPanel
*panel
)
416 while (panel
->dir
.list
[current_file
].f
.marked
== 0)
419 return panel
->dir
.list
[current_file
].fname
;
422 /* --------------------------------------------------------------------------------------------- */
425 try_chmod (const vfs_path_t
*p
, mode_t m
)
427 const char *fname
= NULL
;
429 while (mc_chmod (p
, m
) == -1 && !ignore_all
)
431 int my_errno
= errno
;
436 fname
= x_basename (vfs_path_as_str (p
));
437 msg
= g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname
, unix_error_string (my_errno
));
439 query_dialog (MSG_ERROR
, msg
, D_ERROR
, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
455 /* retry this file */
460 /* stop remain files processing */
468 /* --------------------------------------------------------------------------------------------- */
471 do_chmod (WPanel
*panel
, const vfs_path_t
*p
, struct stat
*sf
)
475 sf
->st_mode
&= and_mask
;
476 sf
->st_mode
|= or_mask
;
478 ret
= try_chmod (p
, sf
->st_mode
);
480 do_file_mark (panel
, current_file
, 0);
485 /* --------------------------------------------------------------------------------------------- */
488 apply_mask (WPanel
*panel
, vfs_path_t
*vpath
, struct stat
*sf
)
492 if (!do_chmod (panel
, vpath
, sf
))
497 const GString
*fname
;
499 fname
= next_file (panel
);
500 vpath
= vfs_path_from_str (fname
->str
);
501 ok
= (mc_stat (vpath
, sf
) == 0);
505 /* if current file was deleted outside mc -- try next file */
506 /* decrease panel->marked */
507 do_file_mark (panel
, current_file
, 0);
514 ch_mode
= sf
->st_mode
;
516 ok
= do_chmod (panel
, vpath
, sf
);
519 vfs_path_free (vpath
, TRUE
);
521 while (ok
&& panel
->marked
!= 0);
524 /* --------------------------------------------------------------------------------------------- */
525 /*** public functions ****************************************************************************/
526 /* --------------------------------------------------------------------------------------------- */
529 chmod_cmd (WPanel
*panel
)
531 gboolean need_update
;
540 { /* do while any files remaining */
544 const GString
*fname
;
552 if (panel
->marked
!= 0)
553 fname
= next_file (panel
); /* next marked file */
557 const file_entry_t
*fe
;
559 fe
= panel_current_entry (panel
);
566 vpath
= vfs_path_from_str (fname
->str
);
568 if (mc_stat (vpath
, &sf_stat
) != 0)
570 vfs_path_free (vpath
, TRUE
);
574 ch_mode
= sf_stat
.st_mode
;
576 ch_dlg
= chmod_dlg_create (panel
, fname
->str
, &sf_stat
);
577 result
= dlg_run (ch_dlg
);
588 if (panel
->marked
<= 1)
590 /* single or last file */
591 if (mc_chmod (vpath
, ch_mode
) == -1 && !ignore_all
)
592 message (D_ERROR
, MSG_ERROR
, _("Cannot chmod \"%s\"\n%s"), fname
->str
,
593 unix_error_string (errno
));
596 else if (!try_chmod (vpath
, ch_mode
))
598 /* stop multiple files processing */
609 and_mask
= or_mask
= 0;
610 and_mask
= ~and_mask
;
612 for (i
= 0; i
< BUTTONS_PERM
; i
++)
613 if (check_perm
[i
].selected
|| result
== B_SETALL
)
615 if (check_perm
[i
].check
->state
)
616 or_mask
|= check_perm
[i
].mode
;
618 and_mask
&= ~check_perm
[i
].mode
;
621 apply_mask (panel
, vpath
, &sf_stat
);
627 and_mask
= or_mask
= 0;
628 and_mask
= ~and_mask
;
630 for (i
= 0; i
< BUTTONS_PERM
; i
++)
631 if (check_perm
[i
].selected
)
632 or_mask
|= check_perm
[i
].mode
;
634 apply_mask (panel
, vpath
, &sf_stat
);
640 and_mask
= or_mask
= 0;
641 and_mask
= ~and_mask
;
643 for (i
= 0; i
< BUTTONS_PERM
; i
++)
644 if (check_perm
[i
].selected
)
645 and_mask
&= ~check_perm
[i
].mode
;
647 apply_mask (panel
, vpath
, &sf_stat
);
656 if (panel
->marked
!= 0 && result
!= B_CANCEL
)
658 do_file_mark (panel
, current_file
, 0);
662 vfs_path_free (vpath
, TRUE
);
664 widget_destroy (WIDGET (ch_dlg
));
666 while (panel
->marked
!= 0 && !end_chmod
);
668 chmod_done (need_update
);
671 /* --------------------------------------------------------------------------------------------- */