root/src/filemanager/chmod.c

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

DEFINITIONS

This source file includes following definitions.
  1. chmod_init
  2. chmod_draw_select
  3. chmod_toggle_select
  4. chmod_refresh
  5. chmod_bg_callback
  6. chmod_callback
  7. chmod_dlg_create
  8. chmod_done
  9. next_file
  10. try_chmod
  11. do_chmod
  12. apply_mask
  13. chmod_cmd

   1 /*
   2    Chmod command -- for the Midnight Commander
   3 
   4    Copyright (C) 1994-2021
   5    Free Software Foundation, Inc.
   6 
   7    This file is part of the Midnight Commander.
   8 
   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.
  13 
  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.
  18 
  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/>.
  21  */
  22 
  23 /** \file chmod.c
  24  *  \brief Source: chmod command
  25  */
  26 
  27 #include <config.h>
  28 
  29 #include <errno.h>
  30 #include <sys/types.h>
  31 #include <sys/stat.h>
  32 #include <unistd.h>
  33 
  34 #include "lib/global.h"
  35 
  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"
  42 
  43 #include "cmd.h"                /* chmod_cmd() */
  44 
  45 /*** global variables ****************************************************************************/
  46 
  47 /*** file scope macro definitions ****************************************************************/
  48 
  49 #define PX 3
  50 #define PY 2
  51 
  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)
  56 
  57 #define BUTTONS      6
  58 #define BUTTONS_PERM 12
  59 #define LABELS       4
  60 
  61 /*** file scope type declarations ****************************************************************/
  62 
  63 /*** file scope variables ************************************************************************/
  64 
  65 static struct
  66 {
  67     mode_t mode;
  68     const char *text;
  69     gboolean selected;
  70     WCheck *check;
  71 } check_perm[BUTTONS_PERM] =
  72 {
  73     /* *INDENT-OFF* */
  74     { S_ISUID, N_("set &user ID on execution"),  FALSE, NULL },
  75     { S_ISGID, N_("set &group ID on execution"), FALSE, NULL },
  76     { S_ISVTX, N_("stick&y bit"),                FALSE, NULL },
  77     { S_IRUSR, N_("&read by owner"),             FALSE, NULL },
  78     { S_IWUSR, N_("&write by owner"),            FALSE, NULL },
  79     { S_IXUSR, N_("e&xecute/search by owner"),   FALSE, NULL },
  80     { S_IRGRP, N_("rea&d by group"),             FALSE, NULL },
  81     { S_IWGRP, N_("write by grou&p"),            FALSE, NULL },
  82     { S_IXGRP, N_("execu&te/search by group"),   FALSE, NULL },
  83     { S_IROTH, N_("read &by others"),            FALSE, NULL },
  84     { S_IWOTH, N_("wr&ite by others"),           FALSE, NULL },
  85     { S_IXOTH, N_("execute/searc&h by others"),  FALSE, NULL }
  86     /* *INDENT-ON* */
  87 };
  88 
  89 static int check_perm_len = 0;
  90 
  91 static const char *file_info_labels[LABELS] = {
  92     N_("Name:"),
  93     N_("Permissions (octal):"),
  94     N_("Owner name:"),
  95     N_("Group name:")
  96 };
  97 
  98 static int file_info_labels_len = 0;
  99 
 100 static struct
 101 {
 102     int ret_cmd;
 103     button_flags_t flags;
 104     int y;                      /* vertical position relatively to dialog bottom boundary */
 105     int len;
 106     const char *text;
 107 } chmod_but[BUTTONS] =
 108 {
 109     /* *INDENT-OFF* */
 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")       }
 116     /* *INDENT-ON* */
 117 };
 118 
 119 static gboolean mode_change;
 120 static int current_file;
 121 static gboolean ignore_all;
 122 
 123 static mode_t and_mask, or_mask, ch_mode;
 124 
 125 static WLabel *statl;
 126 static WGroupbox *file_gb;
 127 
 128 /* --------------------------------------------------------------------------------------------- */
 129 /*** file scope functions ************************************************************************/
 130 /* --------------------------------------------------------------------------------------------- */
 131 
 132 static void
 133 chmod_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 134 {
 135     static gboolean i18n = FALSE;
 136     int i, len;
 137 
 138     for (i = 0; i < BUTTONS_PERM; i++)
 139         check_perm[i].selected = FALSE;
 140 
 141     if (i18n)
 142         return;
 143 
 144     i18n = TRUE;
 145 
 146 #ifdef ENABLE_NLS
 147     for (i = 0; i < BUTTONS_PERM; i++)
 148         check_perm[i].text = _(check_perm[i].text);
 149 
 150     for (i = 0; i < LABELS; i++)
 151         file_info_labels[i] = _(file_info_labels[i]);
 152 
 153     for (i = 0; i < BUTTONS; i++)
 154         chmod_but[i].text = _(chmod_but[i].text);
 155 #endif /* ENABLE_NLS */
 156 
 157     for (i = 0; i < BUTTONS_PERM; i++)
 158     {
 159         len = str_term_width1 (check_perm[i].text);
 160         check_perm_len = MAX (check_perm_len, len);
 161     }
 162 
 163     check_perm_len += 1 + 3 + 1;        /* mark, [x] and space */
 164 
 165     for (i = 0; i < LABELS; i++)
 166     {
 167         len = str_term_width1 (file_info_labels[i]) + 2;        /* spaces around */
 168         file_info_labels_len = MAX (file_info_labels_len, len);
 169     }
 170 
 171     for (i = 0; i < BUTTONS; i++)
 172     {
 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;      /* <> */
 176     }
 177 }
 178 
 179 /* --------------------------------------------------------------------------------------------- */
 180 
 181 static void
 182 chmod_draw_select (const WDialog * h, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 183 {
 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);
 187 }
 188 
 189 /* --------------------------------------------------------------------------------------------- */
 190 
 191 static void
 192 chmod_toggle_select (const WDialog * h, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 193 {
 194     check_perm[Id].selected = !check_perm[Id].selected;
 195     tty_setcolor (COLOR_NORMAL);
 196     chmod_draw_select (h, Id);
 197 }
 198 
 199 /* --------------------------------------------------------------------------------------------- */
 200 
 201 static void
 202 chmod_refresh (const WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 203 {
 204     int i;
 205     int y, x;
 206 
 207     tty_setcolor (COLOR_NORMAL);
 208 
 209     for (i = 0; i < BUTTONS_PERM; i++)
 210         chmod_draw_select (h, i);
 211 
 212     y = WIDGET (file_gb)->y + 1;
 213     x = WIDGET (file_gb)->x + 2;
 214 
 215     tty_gotoyx (y, x);
 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]);
 223 }
 224 
 225 /* --------------------------------------------------------------------------------------------- */
 226 
 227 static cb_ret_t
 228 chmod_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 229 {
 230     switch (msg)
 231     {
 232     case MSG_DRAW:
 233         frame_callback (w, NULL, MSG_DRAW, 0, NULL);
 234         chmod_refresh (CONST_DIALOG (w->owner));
 235         return MSG_HANDLED;
 236 
 237     default:
 238         return frame_callback (w, sender, msg, parm, data);
 239     }
 240 }
 241 
 242 /* --------------------------------------------------------------------------------------------- */
 243 
 244 static cb_ret_t
 245 chmod_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 246 {
 247     WGroup *g = GROUP (w);
 248     WDialog *h = DIALOG (w);
 249 
 250     switch (msg)
 251     {
 252     case MSG_NOTIFY:
 253         {
 254             /* handle checkboxes */
 255             int i;
 256 
 257             /* whether notification was sent by checkbox? */
 258             for (i = 0; i < BUTTONS_PERM; i++)
 259                 if (sender == WIDGET (check_perm[i].check))
 260                     break;
 261 
 262             if (i < BUTTONS_PERM)
 263             {
 264                 ch_mode ^= check_perm[i].mode;
 265                 label_set_textv (statl, "%o", (unsigned int) ch_mode);
 266                 chmod_toggle_select (h, i);
 267                 mode_change = TRUE;
 268                 return MSG_HANDLED;
 269             }
 270         }
 271 
 272         return MSG_NOT_HANDLED;
 273 
 274     case MSG_KEY:
 275         if (parm == 'T' || parm == 't' || parm == KEY_IC)
 276         {
 277             int i;
 278             unsigned long id;
 279 
 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)
 283                     break;
 284 
 285             if (i < BUTTONS_PERM)
 286             {
 287                 chmod_toggle_select (h, i);
 288                 if (parm == KEY_IC)
 289                     group_select_next_widget (g);
 290                 return MSG_HANDLED;
 291             }
 292         }
 293         return MSG_NOT_HANDLED;
 294 
 295     default:
 296         return dlg_default_callback (w, sender, msg, parm, data);
 297     }
 298 }
 299 
 300 /* --------------------------------------------------------------------------------------------- */
 301 
 302 static WDialog *
 303 chmod_dlg_create (WPanel * panel, const char *fname, const struct stat *sf_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
 304 {
 305     gboolean single_set;
 306     WDialog *ch_dlg;
 307     WGroup *g;
 308     int lines, cols;
 309     int i, y;
 310     int perm_gb_len;
 311     int file_gb_len;
 312     const char *c_fname, *c_fown, *c_fgrp;
 313     char buffer[BUF_TINY];
 314 
 315     mode_change = FALSE;
 316 
 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);
 322 
 323     lines = single_set ? 20 : 23;
 324     cols = perm_gb_len + file_gb_len + 1 + 6;
 325 
 326     if (cols > COLS)
 327     {
 328         /* shrink the right groupbox */
 329         cols = COLS;
 330         file_gb_len = cols - (perm_gb_len + 1 + 6);
 331     }
 332 
 333     ch_dlg =
 334         dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
 335                     chmod_callback, NULL, "[Chmod]", _("Chmod command"));
 336     g = GROUP (ch_dlg);
 337 
 338     /* draw background */
 339     ch_dlg->bg->callback = chmod_bg_callback;
 340 
 341     group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _("Permission")));
 342 
 343     for (i = 0; i < BUTTONS_PERM; i++)
 344     {
 345         check_perm[i].check = check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0,
 346                                          check_perm[i].text);
 347         group_add_widget (g, check_perm[i].check);
 348     }
 349 
 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);
 352 
 353     /* Set the labels */
 354     y = PY + 2;
 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));
 365 
 366     if (!single_set)
 367     {
 368         i = 0;
 369 
 370         group_add_widget (g, hline_new (lines - chmod_but[i].y - 1, -1, -1));
 371 
 372         for (; i < BUTTONS - 2; i++)
 373         {
 374             y = lines - chmod_but[i].y;
 375             group_add_widget (g, button_new (y, WIDGET (ch_dlg)->cols / 2 - chmod_but[i].len,
 376                                              chmod_but[i].ret_cmd, chmod_but[i].flags,
 377                                              chmod_but[i].text, NULL));
 378             i++;
 379             group_add_widget (g, button_new (y, WIDGET (ch_dlg)->cols / 2 + 1,
 380                                              chmod_but[i].ret_cmd, chmod_but[i].flags,
 381                                              chmod_but[i].text, NULL));
 382         }
 383     }
 384 
 385     i = BUTTONS - 2;
 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)->cols / 2 - chmod_but[i].len,
 389                                      chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
 390                                      NULL));
 391     i++;
 392     group_add_widget (g, button_new (y, WIDGET (ch_dlg)->cols / 2 + 1, chmod_but[i].ret_cmd,
 393                                      chmod_but[i].flags, chmod_but[i].text, NULL));
 394 
 395     /* select first checkbox */
 396     widget_select (WIDGET (check_perm[0].check));
 397 
 398     return ch_dlg;
 399 }
 400 
 401 /* --------------------------------------------------------------------------------------------- */
 402 
 403 static void
 404 chmod_done (gboolean need_update)
     /* [previous][next][first][last][top][bottom][index][help]  */
 405 {
 406     if (need_update)
 407         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
 408     repaint_screen ();
 409 }
 410 
 411 /* --------------------------------------------------------------------------------------------- */
 412 
 413 static const GString *
 414 next_file (const WPanel * panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 415 {
 416     while (!panel->dir.list[current_file].f.marked)
 417         current_file++;
 418 
 419     return panel->dir.list[current_file].fname;
 420 }
 421 
 422 /* --------------------------------------------------------------------------------------------- */
 423 
 424 static gboolean
 425 try_chmod (const vfs_path_t * p, mode_t m)
     /* [previous][next][first][last][top][bottom][index][help]  */
 426 {
 427     while (mc_chmod (p, m) == -1 && !ignore_all)
 428     {
 429         int my_errno = errno;
 430         int result;
 431         char *msg;
 432 
 433         msg =
 434             g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), x_basename (vfs_path_as_str (p)),
 435                              unix_error_string (my_errno));
 436         result =
 437             query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
 438                           _("&Cancel"));
 439         g_free (msg);
 440 
 441         switch (result)
 442         {
 443         case 0:
 444             /* try next file */
 445             return TRUE;
 446 
 447         case 1:
 448             ignore_all = TRUE;
 449             /* try next file */
 450             return TRUE;
 451 
 452         case 2:
 453             /* retry this file */
 454             break;
 455 
 456         case 3:
 457         default:
 458             /* stop remain files processing */
 459             return FALSE;
 460         }
 461     }
 462 
 463     return TRUE;
 464 }
 465 
 466 /* --------------------------------------------------------------------------------------------- */
 467 
 468 static gboolean
 469 do_chmod (WPanel * panel, const vfs_path_t * p, struct stat *sf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 470 {
 471     gboolean ret;
 472 
 473     sf->st_mode &= and_mask;
 474     sf->st_mode |= or_mask;
 475 
 476     ret = try_chmod (p, sf->st_mode);
 477 
 478     do_file_mark (panel, current_file, 0);
 479 
 480     return ret;
 481 }
 482 
 483 /* --------------------------------------------------------------------------------------------- */
 484 
 485 static void
 486 apply_mask (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 487 {
 488     gboolean ok;
 489 
 490     if (!do_chmod (panel, vpath, sf))
 491         return;
 492 
 493     do
 494     {
 495         const GString *fname;
 496 
 497         fname = next_file (panel);
 498         vpath = vfs_path_from_str (fname->str);
 499         ok = (mc_stat (vpath, sf) == 0);
 500 
 501         if (!ok)
 502         {
 503             /* if current file was deleted outside mc -- try next file */
 504             /* decrease panel->marked */
 505             do_file_mark (panel, current_file, 0);
 506 
 507             /* try next file */
 508             ok = TRUE;
 509         }
 510         else
 511         {
 512             ch_mode = sf->st_mode;
 513 
 514             ok = do_chmod (panel, vpath, sf);
 515         }
 516 
 517         vfs_path_free (vpath, TRUE);
 518     }
 519     while (ok && panel->marked != 0);
 520 }
 521 
 522 /* --------------------------------------------------------------------------------------------- */
 523 /*** public functions ****************************************************************************/
 524 /* --------------------------------------------------------------------------------------------- */
 525 
 526 void
 527 chmod_cmd (WPanel * panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 528 {
 529     gboolean need_update;
 530     gboolean end_chmod;
 531 
 532     chmod_init ();
 533 
 534     current_file = 0;
 535     ignore_all = FALSE;
 536 
 537     do
 538     {                           /* do while any files remaining */
 539         vfs_path_t *vpath;
 540         WDialog *ch_dlg;
 541         struct stat sf_stat;
 542         const GString *fname;
 543         int i, result;
 544 
 545         do_refresh ();
 546 
 547         need_update = FALSE;
 548         end_chmod = FALSE;
 549 
 550         if (panel->marked != 0)
 551             fname = next_file (panel);  /* next marked file */
 552         else
 553             fname = selection (panel)->fname;   /* single file */
 554 
 555         vpath = vfs_path_from_str (fname->str);
 556 
 557         if (mc_stat (vpath, &sf_stat) != 0)
 558         {
 559             vfs_path_free (vpath, TRUE);
 560             break;
 561         }
 562 
 563         ch_mode = sf_stat.st_mode;
 564 
 565         ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat);
 566         result = dlg_run (ch_dlg);
 567 
 568         switch (result)
 569         {
 570         case B_CANCEL:
 571             end_chmod = TRUE;
 572             break;
 573 
 574         case B_ENTER:
 575             if (mode_change)
 576             {
 577                 if (panel->marked <= 1)
 578                 {
 579                     /* single or last file */
 580                     if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
 581                         message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), fname->str,
 582                                  unix_error_string (errno));
 583                     end_chmod = TRUE;
 584                 }
 585                 else if (!try_chmod (vpath, ch_mode))
 586                 {
 587                     /* stop multiple files processing */
 588                     result = B_CANCEL;
 589                     end_chmod = TRUE;
 590                 }
 591             }
 592 
 593             need_update = TRUE;
 594             break;
 595 
 596         case B_SETALL:
 597         case B_MARKED:
 598             and_mask = or_mask = 0;
 599             and_mask = ~and_mask;
 600 
 601             for (i = 0; i < BUTTONS_PERM; i++)
 602                 if (check_perm[i].selected || result == B_SETALL)
 603                 {
 604                     if (check_perm[i].check->state)
 605                         or_mask |= check_perm[i].mode;
 606                     else
 607                         and_mask &= ~check_perm[i].mode;
 608                 }
 609 
 610             apply_mask (panel, vpath, &sf_stat);
 611             need_update = TRUE;
 612             end_chmod = TRUE;
 613             break;
 614 
 615         case B_SETMRK:
 616             and_mask = or_mask = 0;
 617             and_mask = ~and_mask;
 618 
 619             for (i = 0; i < BUTTONS_PERM; i++)
 620                 if (check_perm[i].selected)
 621                     or_mask |= check_perm[i].mode;
 622 
 623             apply_mask (panel, vpath, &sf_stat);
 624             need_update = TRUE;
 625             end_chmod = TRUE;
 626             break;
 627 
 628         case B_CLRMRK:
 629             and_mask = or_mask = 0;
 630             and_mask = ~and_mask;
 631 
 632             for (i = 0; i < BUTTONS_PERM; i++)
 633                 if (check_perm[i].selected)
 634                     and_mask &= ~check_perm[i].mode;
 635 
 636             apply_mask (panel, vpath, &sf_stat);
 637             need_update = TRUE;
 638             end_chmod = TRUE;
 639             break;
 640 
 641         default:
 642             break;
 643         }
 644 
 645         if (panel->marked != 0 && result != B_CANCEL)
 646         {
 647             do_file_mark (panel, current_file, 0);
 648             need_update = TRUE;
 649         }
 650 
 651         vfs_path_free (vpath, TRUE);
 652 
 653         dlg_destroy (ch_dlg);
 654     }
 655     while (panel->marked != 0 && !end_chmod);
 656 
 657     chmod_done (need_update);
 658 }
 659 
 660 /* --------------------------------------------------------------------------------------------- */

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