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

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