Implement cancel of chmod of many files.
[midnight-commander.git] / src / filemanager / chmod.c
blob997d79febc74a1fd58ea9a95947cdcdeefd4c974
1 /*
2 Chmod command -- for the Midnight Commander
4 Copyright (C) 1994-2017
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/>.
23 /** \file chmod.c
24 * \brief Source: chmod command
27 #include <config.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
34 #include "lib/global.h"
36 #include "lib/tty/tty.h"
37 #include "lib/skin.h"
38 #include "lib/vfs/vfs.h"
39 #include "lib/strutil.h"
40 #include "lib/util.h"
41 #include "lib/widget.h"
43 #include "midnight.h" /* current_panel */
45 #include "chmod.h"
47 /*** global variables ****************************************************************************/
49 /*** file scope macro definitions ****************************************************************/
51 #define PX 3
52 #define PY 2
54 #define B_MARKED B_USER
55 #define B_SETALL (B_USER + 1)
56 #define B_SETMRK (B_USER + 2)
57 #define B_CLRMRK (B_USER + 3)
59 #define BUTTONS 6
60 #define BUTTONS_PERM 12
61 #define LABELS 4
63 /*** file scope type declarations ****************************************************************/
65 /*** file scope variables ************************************************************************/
67 static struct
69 mode_t mode;
70 const char *text;
71 gboolean selected;
72 WCheck *check;
73 } check_perm[BUTTONS_PERM] =
75 /* *INDENT-OFF* */
76 { S_ISUID, N_("set &user ID on execution"), FALSE, NULL },
77 { S_ISGID, N_("set &group ID on execution"), FALSE, NULL },
78 { S_ISVTX, N_("stick&y bit"), FALSE, NULL },
79 { S_IRUSR, N_("&read by owner"), FALSE, NULL },
80 { S_IWUSR, N_("&write by owner"), FALSE, NULL },
81 { S_IXUSR, N_("e&xecute/search by owner"), FALSE, NULL },
82 { S_IRGRP, N_("rea&d by group"), FALSE, NULL },
83 { S_IWGRP, N_("write by grou&p"), FALSE, NULL },
84 { S_IXGRP, N_("execu&te/search by group"), FALSE, NULL },
85 { S_IROTH, N_("read &by others"), FALSE, NULL },
86 { S_IWOTH, N_("wr&ite by others"), FALSE, NULL },
87 { S_IXOTH, N_("execute/searc&h by others"), FALSE, NULL }
88 /* *INDENT-ON* */
91 static int check_perm_len = 0;
93 static const char *file_info_labels[LABELS] = {
94 N_("Name:"),
95 N_("Permissions (octal):"),
96 N_("Owner name:"),
97 N_("Group name:")
100 static int file_info_labels_len = 0;
102 static struct
104 int ret_cmd;
105 button_flags_t flags;
106 int y; /* vertical position relatively to dialog bottom boundary */
107 int len;
108 const char *text;
109 } chmod_but[BUTTONS] =
111 /* *INDENT-OFF* */
112 { B_SETALL, NORMAL_BUTTON, 6, 0, N_("Set &all") },
113 { B_MARKED, NORMAL_BUTTON, 6, 0, N_("&Marked all") },
114 { B_SETMRK, NORMAL_BUTTON, 5, 0, N_("S&et marked") },
115 { B_CLRMRK, NORMAL_BUTTON, 5, 0, N_("C&lear marked") },
116 { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") },
117 { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") }
118 /* *INDENT-ON* */
121 static gboolean mode_change;
122 static int current_file;
123 static gboolean ignore_all;
125 static mode_t and_mask, or_mask, ch_mode;
127 static WLabel *statl;
128 static WGroupbox *file_gb;
130 /* --------------------------------------------------------------------------------------------- */
131 /*** file scope functions ************************************************************************/
132 /* --------------------------------------------------------------------------------------------- */
134 static void
135 chmod_i18n (void)
137 static gboolean i18n = FALSE;
138 int i, len;
140 if (i18n)
141 return;
143 i18n = TRUE;
145 #ifdef ENABLE_NLS
146 for (i = 0; i < BUTTONS_PERM; i++)
147 check_perm[i].text = _(check_perm[i].text);
149 for (i = 0; i < LABELS; i++)
150 file_info_labels[i] = _(file_info_labels[i]);
152 for (i = 0; i < BUTTONS; i++)
153 chmod_but[i].text = _(chmod_but[i].text);
154 #endif /* ENABLE_NLS */
156 for (i = 0; i < BUTTONS_PERM; i++)
158 len = str_term_width1 (check_perm[i].text);
159 check_perm_len = MAX (check_perm_len, len);
162 check_perm_len += 1 + 3 + 1; /* mark, [x] and space */
164 for (i = 0; i < LABELS; i++)
166 len = str_term_width1 (file_info_labels[i]) + 2; /* spaces around */
167 file_info_labels_len = MAX (file_info_labels_len, len);
170 for (i = 0; i < BUTTONS; i++)
172 chmod_but[i].len = str_term_width1 (chmod_but[i].text) + 3; /* [], spaces and w/o & */
173 if (chmod_but[i].flags == DEFPUSH_BUTTON)
174 chmod_but[i].len += 2; /* <> */
178 /* --------------------------------------------------------------------------------------------- */
180 static void
181 chmod_toggle_select (WDialog * h, int Id)
183 tty_setcolor (COLOR_NORMAL);
184 check_perm[Id].selected = !check_perm[Id].selected;
186 widget_move (h, PY + Id + 1, PX + 1);
187 tty_print_char (check_perm[Id].selected ? '*' : ' ');
188 widget_move (h, PY + Id + 1, PX + 3);
191 /* --------------------------------------------------------------------------------------------- */
193 static void
194 chmod_refresh (WDialog * h)
196 int y = WIDGET (file_gb)->y + 1;
197 int x = WIDGET (file_gb)->x + 2;
199 dlg_default_repaint (h);
201 tty_setcolor (COLOR_NORMAL);
203 tty_gotoyx (y, x);
204 tty_print_string (file_info_labels[0]);
205 tty_gotoyx (y + 2, x);
206 tty_print_string (file_info_labels[1]);
207 tty_gotoyx (y + 4, x);
208 tty_print_string (file_info_labels[2]);
209 tty_gotoyx (y + 6, x);
210 tty_print_string (file_info_labels[3]);
213 /* --------------------------------------------------------------------------------------------- */
215 static cb_ret_t
216 chmod_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
218 WDialog *h = DIALOG (w);
220 switch (msg)
222 case MSG_DRAW:
223 chmod_refresh (h);
224 return MSG_HANDLED;
226 case MSG_NOTIFY:
228 /* handle checkboxes */
229 int i;
231 /* whether notification was sent by checkbox? */
232 for (i = 0; i < BUTTONS_PERM; i++)
233 if (sender == WIDGET (check_perm[i].check))
234 break;
236 if (i < BUTTONS_PERM)
238 char buffer[BUF_TINY];
240 ch_mode ^= check_perm[i].mode;
241 g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
242 label_set_text (statl, buffer);
243 chmod_toggle_select (h, i);
244 mode_change = TRUE;
245 return MSG_HANDLED;
249 return MSG_NOT_HANDLED;
251 case MSG_KEY:
252 if (parm == 'T' || parm == 't' || parm == KEY_IC)
254 int i;
255 unsigned long id;
257 id = dlg_get_current_widget_id (h);
258 for (i = 0; i < BUTTONS_PERM; i++)
259 if (id == WIDGET (check_perm[i].check)->id)
260 break;
262 if (i < BUTTONS_PERM)
264 chmod_toggle_select (h, i);
265 if (parm == KEY_IC)
266 dlg_select_next_widget (h);
267 return MSG_HANDLED;
270 return MSG_NOT_HANDLED;
272 default:
273 return dlg_default_callback (w, sender, msg, parm, data);
277 /* --------------------------------------------------------------------------------------------- */
279 static WDialog *
280 chmod_init (const char *fname, const struct stat *sf_stat)
282 gboolean single_set;
283 WDialog *ch_dlg;
284 int lines, cols;
285 int i, y;
286 int perm_gb_len;
287 int file_gb_len;
288 const char *c_fname, *c_fown, *c_fgrp;
289 char buffer[BUF_TINY];
291 mode_change = FALSE;
293 single_set = (current_panel->marked < 2);
294 perm_gb_len = check_perm_len + 2;
295 file_gb_len = file_info_labels_len + 2;
296 cols = str_term_width1 (fname) + 2 + 1;
297 file_gb_len = MAX (file_gb_len, cols);
299 lines = single_set ? 20 : 23;
300 cols = perm_gb_len + file_gb_len + 1 + 6;
302 if (cols > COLS)
304 /* shrink the right groupbox */
305 cols = COLS;
306 file_gb_len = cols - (perm_gb_len + 1 + 6);
309 ch_dlg =
310 dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
311 chmod_callback, NULL, "[Chmod]", _("Chmod command"));
313 add_widget (ch_dlg, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _("Permission")));
315 for (i = 0; i < BUTTONS_PERM; i++)
317 check_perm[i].check = check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0,
318 check_perm[i].text);
319 add_widget (ch_dlg, check_perm[i].check);
322 file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _("File"));
323 add_widget (ch_dlg, file_gb);
325 /* Set the labels */
326 y = PY + 2;
327 cols = PX + perm_gb_len + 3;
328 c_fname = str_trunc (fname, file_gb_len - 3);
329 add_widget (ch_dlg, label_new (y, cols, c_fname));
330 g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
331 statl = label_new (y + 2, cols, buffer);
332 add_widget (ch_dlg, statl);
333 c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3);
334 add_widget (ch_dlg, label_new (y + 4, cols, c_fown));
335 c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3);
336 add_widget (ch_dlg, label_new (y + 6, cols, c_fgrp));
338 if (!single_set)
340 i = 0;
341 add_widget (ch_dlg, hline_new (lines - chmod_but[i].y - 1, -1, -1));
342 for (; i < BUTTONS - 2; i++)
344 y = lines - chmod_but[i].y;
345 add_widget (ch_dlg,
346 button_new (y, WIDGET (ch_dlg)->cols / 2 - chmod_but[i].len,
347 chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
348 NULL));
349 i++;
350 add_widget (ch_dlg,
351 button_new (y, WIDGET (ch_dlg)->cols / 2 + 1,
352 chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
353 NULL));
357 i = BUTTONS - 2;
358 y = lines - chmod_but[i].y;
359 add_widget (ch_dlg, hline_new (y - 1, -1, -1));
360 add_widget (ch_dlg,
361 button_new (y, WIDGET (ch_dlg)->cols / 2 - chmod_but[i].len, chmod_but[i].ret_cmd,
362 chmod_but[i].flags, chmod_but[i].text, NULL));
363 i++;
364 add_widget (ch_dlg,
365 button_new (y, WIDGET (ch_dlg)->cols / 2 + 1, chmod_but[i].ret_cmd,
366 chmod_but[i].flags, chmod_but[i].text, NULL));
368 /* select first checkbox */
369 widget_select (WIDGET (check_perm[0].check));
371 return ch_dlg;
374 /* --------------------------------------------------------------------------------------------- */
376 static void
377 chmod_done (gboolean need_update)
379 if (need_update)
380 update_panels (UP_OPTIMIZE, UP_KEEPSEL);
381 repaint_screen ();
384 /* --------------------------------------------------------------------------------------------- */
386 static const char *
387 next_file (void)
389 while (!current_panel->dir.list[current_file].f.marked)
390 current_file++;
392 return current_panel->dir.list[current_file].fname;
395 /* --------------------------------------------------------------------------------------------- */
397 static gboolean
398 try_chmod (const vfs_path_t * p, mode_t m)
400 while (mc_chmod (p, m) == -1 && !ignore_all)
402 int my_errno = errno;
403 int result;
404 char *msg;
406 msg =
407 g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), x_basename (vfs_path_as_str (p)),
408 unix_error_string (my_errno));
409 result =
410 query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
411 _("&Cancel"));
412 g_free (msg);
414 switch (result)
416 case 0:
417 /* try next file */
418 return TRUE;
420 case 1:
421 ignore_all = TRUE;
422 /* try next file */
423 return TRUE;
425 case 2:
426 /* retry this file */
427 break;
429 case 3:
430 default:
431 /* stop remain files processing */
432 return FALSE;
436 return TRUE;
439 /* --------------------------------------------------------------------------------------------- */
441 static gboolean
442 do_chmod (struct stat *sf)
444 gboolean ret;
445 vfs_path_t *vpath;
447 sf->st_mode &= and_mask;
448 sf->st_mode |= or_mask;
450 vpath = vfs_path_from_str (current_panel->dir.list[current_file].fname);
451 ret = try_chmod (vpath, sf->st_mode);
452 vfs_path_free (vpath);
454 do_file_mark (current_panel, current_file, 0);
456 return ret;
459 /* --------------------------------------------------------------------------------------------- */
461 static void
462 apply_mask (struct stat *sf)
464 if (!do_chmod (sf))
465 return;
469 const char *fname;
470 vfs_path_t *vpath;
471 gboolean ok;
473 fname = next_file ();
474 vpath = vfs_path_from_str (fname);
475 ok = (mc_stat (vpath, sf) == 0);
476 vfs_path_free (vpath);
478 if (!ok)
480 /* if current file was deleted outside mc -- try next file */
481 /* decrease current_panel->marked */
482 do_file_mark (current_panel, current_file, 0);
484 else
486 ch_mode = sf->st_mode;
488 if (!do_chmod (sf))
489 return;
492 while (current_panel->marked != 0);
495 /* --------------------------------------------------------------------------------------------- */
496 /*** public functions ****************************************************************************/
497 /* --------------------------------------------------------------------------------------------- */
499 void
500 chmod_cmd (void)
502 gboolean need_update;
503 gboolean end_chmod;
505 chmod_i18n ();
507 current_file = 0;
508 ignore_all = FALSE;
511 { /* do while any files remaining */
512 vfs_path_t *vpath;
513 WDialog *ch_dlg;
514 struct stat sf_stat;
515 const char *fname;
516 int i, result;
518 do_refresh ();
520 need_update = FALSE;
521 end_chmod = FALSE;
523 if (current_panel->marked != 0)
524 fname = next_file (); /* next marked file */
525 else
526 fname = selection (current_panel)->fname; /* single file */
528 vpath = vfs_path_from_str (fname);
530 if (mc_stat (vpath, &sf_stat) != 0)
532 vfs_path_free (vpath);
533 break;
536 ch_mode = sf_stat.st_mode;
538 ch_dlg = chmod_init (fname, &sf_stat);
540 result = dlg_run (ch_dlg);
542 switch (result)
544 case B_CANCEL:
545 end_chmod = TRUE;
546 break;
548 case B_ENTER:
549 if (mode_change)
551 if (current_panel->marked <= 1)
553 /* single or last file */
554 if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
555 message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), fname,
556 unix_error_string (errno));
557 end_chmod = TRUE;
559 else if (!try_chmod (vpath, ch_mode))
561 /* stop multiple files processing */
562 result = B_CANCEL;
563 end_chmod = TRUE;
567 need_update = TRUE;
568 break;
570 case B_SETALL:
571 case B_MARKED:
572 and_mask = or_mask = 0;
573 and_mask = ~and_mask;
575 for (i = 0; i < BUTTONS_PERM; i++)
576 if (check_perm[i].selected || result == B_SETALL)
578 if (check_perm[i].check->state)
579 or_mask |= check_perm[i].mode;
580 else
581 and_mask &= ~check_perm[i].mode;
584 apply_mask (&sf_stat);
585 need_update = TRUE;
586 end_chmod = TRUE;
587 break;
589 case B_SETMRK:
590 and_mask = or_mask = 0;
591 and_mask = ~and_mask;
593 for (i = 0; i < BUTTONS_PERM; i++)
594 if (check_perm[i].selected)
595 or_mask |= check_perm[i].mode;
597 apply_mask (&sf_stat);
598 need_update = TRUE;
599 end_chmod = TRUE;
600 break;
602 case B_CLRMRK:
603 and_mask = or_mask = 0;
604 and_mask = ~and_mask;
606 for (i = 0; i < BUTTONS_PERM; i++)
607 if (check_perm[i].selected)
608 and_mask &= ~check_perm[i].mode;
610 apply_mask (&sf_stat);
611 need_update = TRUE;
612 end_chmod = TRUE;
613 break;
615 default:
616 break;
619 if (current_panel->marked != 0 && result != B_CANCEL)
621 do_file_mark (current_panel, current_file, 0);
622 need_update = TRUE;
625 vfs_path_free (vpath);
627 dlg_destroy (ch_dlg);
629 while (current_panel->marked != 0 && !end_chmod);
631 chmod_done (need_update);
634 /* --------------------------------------------------------------------------------------------- */